尽管
require是同步的,并且Node.js并未提供现成的异步变体,但是您可以轻松地自己构建一个变体。
首先,您需要创建一个模块。在我的示例中,我将编写一个模块,该模块从文件系统异步加载数据,但是当然是YMMV。因此,首先,使用老式的,不需要的同步方法:
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('./passwords')(function (err, passwords) { // This pre runs once the passwords have been loaded.});当然,使用这种方法,您可以将任何任意的同步模块初始化都转换为异步初始化。但是窍门总是一样的:导出一个函数,从
require调用中直接调用它,并提供一个回调,一旦异步代码运行,该回调将继续执行。
请注意,对于某些人
require('...')(function () { ... });可能会令人困惑。因此,使用异步函数或类似的方法导出对象 可能 会更好(尽管这取决于您的实际情况)
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&co。只是回调上的语法糖。
一旦构建了这样的模块,您甚至可以构建一个
requireAsync功能,该功能的运行与您最初在问题中建议的一样。您所要做的就是坚持使用初始化函数的名称,例如
initialize。然后,您可以执行以下操作:
var requireAsync = function (module, callback) { require(module).initialize(callback);};requireAsync('./passwords', function (err, passwords) { // ...});请注意,由于功能的限制,当然, 加载 模块仍将是同步的
require,但是其余所有模块将根据您的需要是异步的。
最后一点:如果要使 加载 模块真正异步, 可以
实现一个函数,该功能
fs.readFile用于异步加载文件,然后通过
eval调用运行该文件以实际执行该模块,但是我 强烈
建议您这样做:一方面,您失去了
request诸如caching&co。之类的所有便利功能,另一方面,您将不得不处理
eval-众所周知,
eval是邪恶的 。所以不要这样做。
不过,如果您 仍然 想要这样做,则基本上它的工作原理如下:
var requireAsync = function (module, callback) { fs.readFile(module, { encoding: 'utf8' }, function (err, data) { var module = { exports: {} }; var pre = '(function (module) {' + data + '})(module)'; eval(pre); 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 pre = '(function (module) {' + data + '})(module)'; vm.runInNewContext(pre, sandbox); callback(null, sandbox.module.exports); // or sandbox.module… });};希望这可以帮助。



