Node.jsの循環参照問題

モジュールの循環参照というNode.jsユーザの間ではよく知られている問題がある。

requireしたモジュールのメソッドやプロパティがundefinedになってしまうという問題だ。これは以下の条件で発生する

  1. モジュールaとモジュールbが互いにrequireしている
    • 間に他のモジュールを挟んでいても発生する、a → b → c → d → e→ aのようなrequireでも発生
  2. module.exports を置き換えている
// a.js
const b = require('./b');

module.exports = {
    print() {
        console.log('a');
    },
    exec() {
        b.print();
    }
};

//  b.js
const a = require('./a');

module.exports = {
    print() {
        console.log('b');
    },
    exec() {
        a.print();
    }
};

// index.js
var a = require('./a');
a.exec();

var b = require('./b');
b.exec();

index.jsを実行すると・・・

$ node index.js
b
/Users/nullpon/b.js:10
        a.print();
          ^

TypeError: a.print is not a function
    at Object.exec (/Users/nullpon/b.js:10:11)
    at Object.<anonymous> (/Users/nullpon/index.js:5:3)
    at Module._compile (module.js:409:26)
    at Object.Module._extensions..js (module.js:416:10)
    at Module.load (module.js:343:32)
    at Function.Module._load (module.js:300:12)
    at Function.Module.runMain (module.js:441:10)
    at startup (node.js:139:18)
    at node.js:990:3

1はモジュール設計の問題。モジュールが互いに依存してしまうのは設計が間違っている証拠である。

どうしても直せない場合はsetImmediateで逃げるという技がある。イベントループの同一のフェーズで相互参照するモジュールをrequireしなければ回避できる。

// a.js
const b = require('./b');

module.exports = {
    print() {
        console.log('a');
    },
    exec() {
        b.print();
    }
};

// b.js
let b;
setImmediate(() => {
  b = require('./b');
});

module.exports.print = function() {
    console.log('a');
};

module.exports.exec = function() {
    b.print();
};

// index.js
var a = require('./a');
a.exec();

var b = require('./b');
setImmediate(() => {  // 次tickに行かないとrequireが完了しない
  b.exec();
});

2に関してはexportsを置き換えるのではなく、exportsを拡張する形でモジュールを作成すると良い。サンプルのコードで言えば以下のようにすると良い。

// b.js
const b = require('./b');

module.exports.print = function() {
    console.log('a');
};

module.exports.exec = function() {
    b.print();
};

ただしclassを作る場合は大抵exportsを置き換える事になるので、これで回避できないことも多い。基本的には相互依存を回避するように設計すべき。

スクリプトのコマンドライン引数にタブを渡す

Node.jsとかRubyとかPythonスクリプトコマンドライン引数を受け取る時、引数にタブ文字を指定する方法

var a = process.argv[2];
console.log([1,2,3].join(a));

実行

> node hoge.js \t
1t2t3
> node hoge.js '\t'
1\t2\t3

正解は

> node hoge.js $'\t'
1   2   3

改行コードなら

> node hoge.js $'\n'
1
2
3