Node.js モジュールを非同期にロードすることは可能ですか?
これは標準コードです:
var foo = require("./foo.js"); // waiting for I/O
foo.bar();
しかし、私は次のようなことを書きたいのです:
require("./foo.js", function(foo) {
foo.bar();
});
// doing something else while the hard drive is crunching...
これを実行する方法はありますか? または、コールバックがrequire
サポートされていないのには何か理由があるのでしょうか?
ベストアンサー1
は同期的ですがrequire
、Node.js ではすぐに使用できる非同期バリアントは提供されていませんが、自分で簡単に構築できます。
まず、モジュールを作成する必要があります。私の例では、ファイル システムからデータを非同期にロードするモジュールを作成しますが、もちろん結果は人によって異なります。まず、望ましくない旧式の同期アプローチから始めます。
var fs = require('fs');
var passwords = fs.readFileSync('/etc/passwd');
module.exports = passwords;
このモジュールは通常どおり使用できます。
var passwords = require('./passwords');
さて、これを非同期モジュールに変換します。遅れmodule.exports の代わりに、非同期で作業を実行し、完了したら呼び戻す関数を即座にエクスポートします。つまり、モジュールを次のように変換します。
var fs = require('fs');
module.exports = function (callback) {
fs.readFile('/etc/passwd', function (err, data) {
callback(err, data);
});
};
もちろん、呼び出しcallback
に変数を直接提供することでこれを短縮できますreadFile
が、ここではデモンストレーションの目的で明示的にしたいと考えました。
このモジュールを require しても、最初は何も起こりません。非同期 (匿名) 関数への参照のみが取得されるからです。必要なのは、すぐにそれを呼び出して、コールバックとして別の関数を提供することです。
require('./passwords')(function (err, passwords) {
// This code runs once the passwords have been loaded.
});
もちろん、このアプローチを使用すると、任意の同期モジュールの初期化を非同期のものに変えることができます。ただし、トリックは常に同じです。関数をエクスポートし、呼び出しから直接呼び出しrequire
、非同期コードの実行後に実行を継続するコールバックを提供します。
一部の人にとっては
require('...')(function () { ... });
混乱するかもしれません。5月initialize
実際のシナリオによって異なりますが、非同期関数などを使用してオブジェクトをエクスポートする方がよいでしょう。
var fs = require('fs');
module.exports = {
initialize: function (callback) {
fs.readFile('/etc/passwd', function (err, data) {
callback(err, data);
});
}
};
このモジュールは次のように使用できます。
require('./passwords').initialize(function (err, passwords) {
// ...
});
少しは読みやすくなるかもしれません。
もちろん、Promise やその他の非同期メカニズムを使用して構文をより見栄えよくすることもできますが、最終的には (内部的には) ここで説明したパターンに常に行き着きます。基本的に、Promise などはコールバックの構文糖に過ぎません。
このようにモジュールを構築すると、requireAsync
質問で最初に提案したように動作する関数を構築することもできます。初期化関数の名前を のように指定するだけですinitialize
。その後、次のように実行できます。
var requireAsync = function (module, callback) {
require(module).initialize(callback);
};
requireAsync('./passwords', function (err, passwords) {
// ...
});
もちろん、読み込み中関数の制限により、モジュールは引き続き同期されますrequire
が、残りはすべて希望どおりに非同期になります。
最後に:実際に読み込み中モジュールは非同期なので、できたfs.readFile
非同期的にファイルをロードする関数を実装し、それをeval
呼び出して実際にモジュールを実行するのですが、非常にこれをお勧めしません。一方では、request
キャッシュなどの便利な機能がすべて失われ、他方ではeval
、誰もが知っているように、評価は悪であるだから、そんなことはしないでください。
それでも、もしあなたがまだやりたいことは、基本的には次のように機能します。
var requireAsync = function (module, callback) {
fs.readFile(module, { encoding: 'utf8' }, function (err, data) {
var module = {
exports: {}
};
var code = '(function (module) {' + data + '})(module)';
eval(code);
callback(null, module);
});
};
このコードは「優れた」ものではなく、エラー処理や元の関数のその他の機能が欠けていることに注意してくださいrequire
。ただし、基本的には、同期設計されたモジュールを非同期でロードできるという要求を満たしています。
とにかく、この関数は次のようなモジュールで使用できます。
module.exports = 'foo';
次のようにロードします:
requireAsync('./foo.js', function (err, module) {
console.log(module.exports); // => 'foo'
});
もちろん、他のものもエクスポートできます。元のrequire
関数との互換性を保つために、次のように実行した方が良いかもしれません。
callback(null, module.exports);
関数の最後の行として、オブジェクト (この場合は文字列)requireAsync
に直接アクセスできるようになります。ロードされたコードをすぐに実行される関数内にラップするため、このモジュール内のすべてのものはプライベートのままとなり、外部の世界への唯一のインターフェースは渡されるオブジェクトになります。exports
foo
module
もちろん、 のこの使用法はevil
セキュリティホールなどを引き起こすので、世界で最高のアイデアではないと主張する人もいるでしょう。しかし、require
モジュールを使用する場合、基本的には を使用すること以外は何もしませんeval
。要点は次のとおりです。コードを信頼しない場合、eval
は と同様に悪いアイデアですrequire
。したがって、この特別なケースでは、問題ない可能性があります。
厳密モードを使用している場合は、eval
これは適切ではありません。vm
モジュールを使用してそのrunInNewContext
機能を使用する必要があります。その場合、解決策は次のようになります。
var requireAsync = function (module, callback) {
fs.readFile(module, { encoding: 'utf8' }, function (err, data) {
var sandbox = {
module: {
exports: {}
}
};
var code = '(function (module) {' + data + '})(module)';
vm.runInNewContext(code, sandbox);
callback(null, sandbox.module.exports); // or sandbox.module…
});
};