Javascript Node.js 模块的异步初始化

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11835096/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 07:17:58  来源:igfitidea点击:

Asynchronous initialization of Node.js module

javascriptnode.jsasynchronous

提问by Aleksei Zabrodskii

I'd like to initialize module in asynchronous way and come up with couple of ideas. I need DB object with list of collections from Mongo and other data, but list of files in ./will do for brevity.

我想以异步方式初始化模块并提出几个想法。我需要带有来自 Mongo 和其他数据的集合列表的 DB 对象,但./为简洁起见,其中的文件列表将起作用。

I can't export function or class because I need require('db')to return same object everytime.

我无法导出函数或类,因为我require('db')每次都需要返回相同的对象。



Firstand simplest what came to my mind is to assign module.exportsto Objectand populate it later:

首先,最简单的东西来到我的脑海里是分配module.exportsObject后来填充它:

var exports = {};
module.exports = exports;

require('fs').readdir('.', function(err, files) {
  exports.error = err;
  exports.files = files;
});

Bad thing — I don't really know from outside when list is ready and no good way to check for errors.

坏事——我真的不知道什么时候列表准备好了,也没有好的方法来检查错误。



Secondway I've comed up with is to inherit EventEmitterand notify everyone that DB is ready or error occured. If everything ok - keep going.

我想出的第二种方法是继承EventEmitter并通知每个人数据库已准备就绪或发生错误。如果一切正常 - 继续。

var events = require('events');
var util = require('util');

function Db() {
  events.EventEmitter.call(this);
  this.ready = false;
  this.files = null;
  this.initialize();
}

util.inherits(Db, events.EventEmitter);

Db.prototype.initialize = function() {
  if (this.ready)
    return this.emit('ready');

  var self = this;
  require('fs').readdir('.', function(err, files) {
    if (err)
      return self.emit('error', err);

    self.files = files;
    self.ready = true;
    self.emit('ready');
  });
};

module.exports = new Db();


And nowI think that's more reasonable:

现在,我认为这是比较合理的:

// db.js
var exports = {init: init};
module.exports = exports;

function init(callback) {
  callback = (typeof callback === 'function') ? callback : function() {};
  require('fs').readdir('.', function(err, files) {
    delete exports.init;
    exports.result = files; // that's pretty much what I need,
                            // so don't mind result slightly differs
                            // from previous cases
    callback(err);
  });
}
// main.js
var db = require('./db');

// check for `db.init` presence maybe...

db.init(function(err) {
  return err ? console.error('Bad!')
             : console.log(db); // It works!
});


What should I pick and why? How bad is such idea in general and my options in particular?

我应该选择什么,为什么?这种想法总体上有多糟糕,尤其是我的选择有多糟糕?

Thanks for feedback.

感谢您的反馈。

回答by zzen

TL;DR:Use readdirSync()instead of readdir()if you're just planning to read local files at startup time. If you're planning to actually read data from remote database or do any I/O at runtime, use your option #2 - the callback. Explanation and code examples below.

TL; DR:使用readdirSync()而不是readdir()如果你只是打算在启动时读取本地文件。如果您打算实际从远程数据库读取数据或在运行时执行任何 I/O,请使用您的选项 #2 - 回调。下面的解释和代码示例。

Detailed explanation:

详细解释:

While at first this might seem like a module/dependecy/require-related question, it's really not. It's a generic question of how to handle asynchronous code. Let me explain:

虽然乍一看这似乎是一个与模块/依赖项/需求相关的问题,但实际上并非如此。这是如何处理异步代码的通用问题。让我解释:

require()is basically the only synchronousfunction widely used throughout node that deals with I/O (it requires other modules from filesystem). Synchronous means it actually returns it's data as return value, instead of calling a callback.

require()基本上是整个节点中唯一广泛使用的处理 I/O 的同步函数(它需要来自文件系统的其他模块)。同步意味着它实际上将数据作为返回值返回,而不是调用回调。

The most basic 101 rule in asynchronous programming is:

异步编程中最基本的101规则是:

You can nevertake an asynchronous piece of code and create a synchronous API for it.

永远无法使用一段异步代码并为其创建同步 API。

requireuses a special synchronousversion of readFilecalled readFileSync. Since modules are really only loaded at the start of the program, the fact that it blocks the node.js execution while it's reading the module is not a problem.

require采用了特殊的同步的版本readFilereadFileSync。由于模块实际上只在程序开始时加载,因此它在读取模块时阻止 node.js 执行的事实不是问题。

In your example however, you try to perform additional asynchronous I/O- readdir()done during the require stage. Thus, you either need to use synchronousversion of this command or the API needs to change...

但是,在您的示例中,您尝试执行额外的异步 I/O-readdir()在 require 阶段完成。因此,您要么需要使用此命令的同步版本,要么需要更改 API...

So there's the background to your problem.

所以这就是你的问题的背景。

You identified the two basic options:

您确定了两个基本选项:

  1. using a promise(which is essentially the same as your EventEmitterexample)
  2. using a callback(your second example shows this well) and a third is:
  3. using a synchronousversion of the readdir()command called readdirSync()
  1. 使用承诺(本质上与您的EventEmitter示例相同)
  2. 使用回调(你的第二个例子很好地展示了这一点),第三个是:
  3. 使用名为的命令的同步版本readdir()readdirSync()

I would use the option #3for simplicity reason - but only if you're planning to just read a couple files at startup time as your example implies. If later your DB module is actually going to connect to a database - or if you're planning to do any of this at runtime, jump the boat now and go with async API.

为简单起见,我会使用选项 #3- 但前提是您计划在启动时只读取几个文件,如您的示例所暗示的那样。如果稍后您的 DB 模块实际上要连接到数据库 - 或者如果您计划在运行时执行任何此类操作,请立即跳船并使用异步 API。

Not many people remember this anymore, but promises were actually the original default of how to handle async in node.js. In node 0.1.30 however promisses were removed and replacedby a standardized callback with the function(err, result)signature. This was done largely for simplicity reasons.

已经没有多少人记得这一点了,但 promise 实际上是如何在 node.js 中处理异步的原始默认值。然而,在节点 0.1.30 中,promises 被删除并替换为带有function(err, result)签名的标准化回调。这样做主要是出于简单的原因。

These days, vast majority of your async calls takes this standard callback as the last parameter. Your database driver does it, your web framework does it - it's everywhere. You should stay with the prevalent design and use it too.

如今,绝大多数异步调用都将此标准回调作为最后一个参数。您的数据库驱动程序执行此操作,您的 Web 框架执行此操作 - 它无处不在。你应该坚持流行的设计并使用它。

The only reason to prefer promises or events is if you have multiple different resultsthat can happen. For example a socket can be opened, receive data, be closed, flushed etc.

偏爱 promise 或 events 的唯一原因是,如果您有多种不同的结果可能发生。例如,可以打开套接字、接收数据、关闭、刷新等。

This is not your case. Your module always does the same (reads some files). So option #2it is (unless you can stay synchronous).

这不是你的情况。你的模块总是做同样的事情(读取一些文件)。所以选项#2是(除非你可以保持同步)。

Finally, here are the two winning options rewritten slightly:

最后,这里是稍微改写的两个获胜选项:

Synchronous option:
good just for local filesystem at startup time

同步选项:
仅适用于启动时的本地文件系统

// db.js
var fs = require('fs');
exports = fs.readdirSync('.');

// main.js
var db = require('./db');
// insert rest of your main.js code here

Asynchronous option:
for when you want to use DBs etc.

异步选项:
当你想使用数据库等时。

// db.js
var fs = require('fs'), cached_files;

exports.init = function(callback) {
  if (cached_files) {
    callback(null, cached_files);
  } else {
    fs.readdir('.', function(err, files) {
      if (!err) {
        cached_files = files;
      }
      callback(err, files);
    });
  }
};

// main.js
require('./db').init(function(err, files) {
  // insert rest of your main.js code here
});

回答by Andrey Sidorov

In general it's very bad idea to have any state in module. Modules should expose functions, not data (yes, this requires to change your code structure a bit). Just pass references to your data to a modules functions as parameters.

一般来说,在模块中有任何状态是非常糟糕的主意。模块应该公开函数,而不是数据(是的,这需要稍微改变你的代码结构)。只需将数据引用作为参数传递给模块函数。

(edit:just realised that this is approach from your last example. My vote for it)

编辑:刚刚意识到这是您上一个示例的方法。我的投票)

module1:

模块1:

module.exports = function(params, callback) { ... }

module2:

模块2:

var createSomething = require('module1');
module.exports = function(params, callback) { 
   ...
   var db = createSomething(params, function(err, res) {
       ...
       callback(err, res);
   }
}

main code:

主要代码:

var createSomethingOther = require('module2');
createSomethingOther(err, result) {
    // do stuff
}

回答by Mariusz Nowak

On my side such module is a function that takes callback (and if internally configured with promises also returns promise (see https://github.com/medikoo/deferred));

在我这边,这样的模块是一个接受回调的函数(如果内部配置了 promises 也会返回 promise(参见https://github.com/medikoo/deferred));

The only problem with callback is that by convention it always should be invoked in nextTick, so even when you call your module function when all the data is gathered you should still call your callback in next tick with result set.

回调的唯一问题是,按照惯例,它始终应该在 nextTick 中调用,因此即使在收集了所有数据时调用模块函数,您仍然应该在带有结果集的下一个刻度中调用回调。