gpt4 book ai didi

javascript - Node.js - 异步模块加载

转载 作者:IT老高 更新时间:2023-10-28 21:53:06 28 4
gpt4 key购买 nike

是否可以异步加载 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 中回调不支持?

最佳答案

虽然 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 code 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 或任何其他异步机制,使您的语法看起来更好,但最终,它(内部)始终归结为我刚刚在这里描述的模式。基本上,promises & co。只不过是回调的语法糖。

像这样构建模块后,您甚至可以构建 requireAsync功能就像您最初在问题中建议的那样工作。您所要做的就是为初始化函数指定一个名称,例如 initialize .然后你可以这样做:
var requireAsync = function (module, callback) {
require(module).initialize(callback);
};

requireAsync('./passwords', function (err, passwords) {
// ...
});

请注意,当然,由于 require 的限制,加载模块仍然是同步的。函数,但所有其余的都将按照您的意愿异步进行。

最后一个注意事项:如果你真的想让加载模块异步,你可以实现一个使用 fs.readFile 的函数。异步加载文件,然后通过 eval 运行它调用实际执行模块,但我会 高度反对此建议:一方面,您将失去 request 的所有便利功能。例如缓存 & co.,另一方面,您将不得不处理 eval - 众所周知,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)。由于您将加载的代码包装在立即执行的函数中,因此该模块中的所有内容都是私有(private)的,与外部世界的唯一接口(interface)是 module。你传入的对象。

当然可以争辩说 evil 的这种用法不是世界上最好的主意,因为它会打开安全漏洞等等——但如果你 require一个模块,你基本上什么都不做,反正除了 eval - 使用它。重点是:如果你不相信代码, evalrequire 是同一个坏主意.因此,在这种特殊情况下,可能没问题。

如果您使用严格模式, 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…
});
};

希望这可以帮助。

关于javascript - Node.js - 异步模块加载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20315434/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com