Node.js模块的异步初始化

时间:2012-08-06 20:01:56

标签: javascript node.js asynchronous

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

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


首先,最简单的想法是将module.exports分配给Object并稍后填充:

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

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

不好的事情 - 当列表准备就绪并且无法检查错误时,我不会从外面知道。


第二种方式我已经开始继承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();

现在我认为这更合理:

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

我应该选择什么?为什么?这个想法总的来说有多糟糕,特别是我的选择?

感谢您的反馈。

3 个答案:

答案 0 :(得分:26)

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

详细说明:

虽然起初这看起来像是一个模块/依赖/需求相关的问题,但事实并非如此。这是如何处理异步代码的一般性问题。让我解释一下:

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

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

  

您可以从不获取异步代码并为其创建同步API。

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

但是,在您的示例中,您尝试在require阶段执行其他异步I / O - readFileSync。因此,您需要使用此命令的同步版本或API需要更改...

所以你的问题有背景。

您确定了两个基本选项:

  1. 使用承诺(与您的readdir()示例基本相同)
  2. 使用回调(你的第二个例子很好地展示了这个) 第三是:
  3. 使用名为EventEmitter
  4. readdir()命令的同步版本

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

    没有多少人记住这一点,但承诺实际上是如何在node.js中处理异步的原始默认值。在节点0.1.30中,通过带有readdirSync()签名的标准化回调,promisses为removed and replaced。这主要是为了简单起见。

    目前,绝大多数异步调用都将此标准回调作为最后一个参数。你的数据库驱动程序就是这样做的,你的web框架就是这样做的 - 它无处不在。你应该保持流行的设计并使用它。

      

    偏好承诺或事件的唯一理由是,如果您有多个不同的结果可能会发生。例如,可以打开套接字,接收数据,关闭,刷新等等。

    这不是你的情况。您的模块始终执行相同操作(读取一些文件)。所以选项#2 是(除非您可以保持同步)。

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

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

    function(err, result)

    异步选项:
    用于何时使用数据库等。

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

答案 1 :(得分:5)

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

编辑:刚刚意识到这是你上一个例子的方法。我对它的投票)

模块1:

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

模块2:

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

主要代码:

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

答案 2 :(得分:1)

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

回调的唯一问题是按照惯例,它总是应该在nextTick中调用,所以即使你在收集所有数据时调用模块函数,你仍然应该在下一个tick中用结果集调用你的回调。