Node.js - 异步模块加载 [英] Node.js - asynchronous module loading

查看:139
本文介绍了Node.js - 异步模块加载的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否可以异步加载Node.js模块?

Is it possible to load a Node.js module asynchronously?

这是标准代码:

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 中的回调?

Is there a way how to do this? Or is there a good reason why callbacks in require aren't supported?

推荐答案

虽然 require 是同步的,而Node.js不提供开箱即用的异步变体,但您可以轻松地为自己构建一个。

While require is synchronous, and Node.js does not provide an asynchronous variant out of the box, you can easily build one for yourself.

首先,您需要创建一个模块。在我的例子中,我将编写一个从文件系统异步加载数据的模块,当然还有YMMV。所以,首先是老式的,不想要的同步方法:

First of all, you need to create a module. In my example I am going to write a module that loads data asynchronously from the file system, but of course YMMV. So, first of all the old-fashioned, not wanted, synchronous approach:

var fs = require('fs');
var passwords = fs.readFileSync('/etc/passwd');

module.exports = passwords;

您可以照常使用此模块:

You can use this module as usual:

var passwords = require('./passwords');

现在,您要做的是将其转换为异步模块。由于你不能延迟 module.exports,你所做的就是立即导出一个异步完成工作的函数,一旦完成就会回调你。因此,您将模块转换为:

Now, what you want to do is turn this into an asynchronous module. As you can not delay module.exports, what you do instead is instantly export a function that does the work asynchronously and calls you back once it is done. So you transform your module into:

var fs = require('fs');
module.exports = function (callback) {
  fs.readFile('/etc/passwd', function (err, data) {
    callback(err, data);
  });
};

当然你可以通过直接提供回调来缩短这个时间变量到 readFile 调用,但为了演示目的,我想在此明确说明。

Of course you can shorten this by directly providing the callback variable to the readFile call, but I wanted to make it explicit here for demonstration purposes.

现在当你需要这个模块时,一开始没有任何反应,因为你只能获得对异步(匿名)函数的引用。您需要做的是立即调用它并提供另一个函数作为回调:

Now when you require this module, at first, nothing happens, as you only get a reference to the asynchronous (anonymous) function. What you need to do is call it right away and provide another function as callback:

require('./passwords')(function (err, passwords) {
  // This code runs once the passwords have been loaded.
});

使用这种方法,您当然可以将任意同步模块初始化转换为异步模块。但技巧总是一样的:导出一个函数,从 require 调用调用它,并提供一个在异步代码运行后继续执行的回调。

Using this approach you can, of course, turn any arbitrary synchronous module initialization to an asynchronous one. But the trick is always the same: Export a function, call it right from the require call and provide a callback that continues execution once the asynchronous code has been run.

请注意,对于某些人来说

Please note that for some people

require('...')(function () { ... });

可能看起来令人困惑。因此,可能更好(尽管这取决于您的实际情况)使用异步初始化函数或类似的东西导出对象:

may look confusing. Hence it may be better (although this depends on your actual scenario) to export an object with an asynchronous initialize function or something like that:

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) {
  // ...
});

可能稍微好一些。

当然你也可以使用promises或任何其他异步机制,使你的语法看起来更好,但最后,它(内部)总是归结为我刚才描述的模式。基本上,承诺&合。只是语法糖而不是回调。

Of course you can also use promises or any other asynchronous mechanism which makes your syntax look nicer, but in the end, it (internally) always comes down to the pattern I just described here. Basically, promises & co. are nothing but syntactic sugar over callbacks.

一旦你构建了这样的模块,你甚至可以构建一个 requireAsync 功能与您最初在问题中建议的功能相同。您所要做的就是坚持使用初始化函数的名称,例如 initialize 。然后你可以这样做:

Once you build your modules like this, you can even build a requireAsync function that works like you initially suggested in your question. All you have to do is stick with a name for the initialization function, such as initialize. Then you can do:

var requireAsync = function (module, callback) {
  require(module).initialize(callback);
};

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

请注意,当然, loading 模块仍然是同步由于 require 函数的限制,但所有其余的都是异步的。

Please note, that, of course, loading the module will still be synchronous due to the limitations of the require function, but all the rest will be asynchronous as you wish.

一个最后注意:如果你想让加载模块异步,你可以实现一个使用 fs.readFile 的函数异步加载文件,然后通过 eval 调用实际执行该模块,但我高度建议不要这样做:一方面,你失去了 request 的所有便利功能,例如缓存和放大另一方面,你将不得不处理 eval - 而众所周知, eval是邪恶的。所以不要这样做。

One final note: If you want to actually make loading modules asynchronous, you could implement a function that uses fs.readFile to asynchronously load a file, and then run it through an eval call to actually execute the module, but I'd highly recommend against this: One the one hand, you lose all the convenience features of request such as caching & co., on the other hand you'll have to deal with eval - and as we all know, eval is evil. So don't do it.

然而,如果仍然想要这样做,基本上它的工作原理如下:

Nevertheless, if you still want to do it, basically it works like this:

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);
  });
};

请注意,此代码不好,并且没有任何错误处理,任何错误处理原始的其他功能需要功能,但基本上,它满足了您能够异步加载同步设计模块的需求。

Please note that this code is not "nice", and that it lacks any error handling, and any other capabilities of the original require function, but basically, it fulfills your demand of being able to asynchronously load synchronously designed modules.

无论如何,你可以使用这个函数,比如

Anyway, you can use this function with a module like

module.exports = 'foo';

并使用以下方式加载:

requireAsync('./foo.js', function (err, module) {
  console.log(module.exports); // => 'foo'
});

当然,您也可以导出其他任何内容。也许,要与原来的 require 功能兼容,最好运行

Of course you can export anything else as well. Maybe, to be compatible with the original require function, it may be better to run

callback(null, module.exports);

作为 requireAsync 函数的最后一行,那么你可以直接访问 exports 对象(在这种情况下是字符串 foo )。由于您将加载的代码包装在一个立即执行的函数中,因此该模块中的所有内容都保持私有,并且外部世界的唯一接口是模块对象传入。

as last line of your requireAsync function, as then you have direct access to the exports object (which is the string foo in this case). Due to the fact that you wrap the loaded code inside of an immediately executed function, everything in this module stays private, and the only interface to the outer world is the module object you pass in.

当然有人可以说这种邪恶的使用并不是世界上最好的主意,因为它打开了安全漏洞等等 - 但是如果你需要一个模块,你基本上什么都不做,无论如何, eval - 取决于它。关键是:如果您不信任代码, eval require 的想法相同。因此,在这种特殊情况下,它可能没问题。

Of course one can argue that this usage of evil is not the best idea in the world, as it opens up security holes and so on - but if you require a module, you basically do nothing else, anyway, than eval-uating it. The point is: If you don't trust the code, eval is the same bad idea as require. Hence in this special case, it might be fine.

如果你使用严格模式, eval 是不好的对于你,你需要使用 vm 模块并使用其 runInNewContext 函数。然后,解决方案如下所示:

If you are using strict mode, eval is no good for you, and you need to go with the vm module and use its runInNewContext function. Then, the solution looks like:

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…
  });
};

希望这有帮助。

这篇关于Node.js - 异步模块加载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆