Node.js模块的异步初始化

Ale*_*kii 21 javascript asynchronous node.js

我想以异步方式初始化模块并提出一些想法.我需要带有来自Mongo和其他数据的集合列表的DB对象,但是./为了简洁起见,文件列表也是如此.

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


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

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

require('fs').readdir('.', function(err, files) {
  exports.error = err;
  exports.files = files;
});
Run Code Online (Sandbox Code Playgroud)

不好的事情 - 当列表准备就绪并且没有检查错误的好方法时,我真的不知道从外面.


我搞砸的第二种方法是继承EventEmitter并通知每个人DB已准备好或发生错误.如果一切正常 - 继续.

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();
Run Code Online (Sandbox Code Playgroud)

现在我认为这更合理:

// 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);
  });
}
Run Code Online (Sandbox Code Playgroud)
// 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!
});
Run Code Online (Sandbox Code Playgroud)

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

感谢您的反馈.

zze*_*zen 26

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

详细说明:

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

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

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

永远不能使用异步代码并为其创建同步API.

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

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

所以你的问题有背景.

您确定了两个基本选项:

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

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

没有多少人记住这一点,但承诺实际上是如何在node.js中处理异步的原始默认值.在节点0.1.30中,但是删除了 promisses 并用带有function(err, result)签名的标准化回调替换.这主要是出于简单的原因.

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

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

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

最后,这里有两个重写的选项:

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

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

// main.js
var db = require('./db');
// insert rest of your main.js code here
Run Code Online (Sandbox Code Playgroud)

异步选项:
用于何时使用DB等

// 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
});
Run Code Online (Sandbox Code Playgroud)


And*_*rov 5

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

(编辑:刚才意识到这是你最后一个例子的方法.我对它的投票)

模块1:

module.exports = function(params, callback) { ... }
Run Code Online (Sandbox Code Playgroud)

模块2:

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

主要代码:

var createSomethingOther = require('module2');
createSomethingOther(err, result) {
    // do stuff
}
Run Code Online (Sandbox Code Playgroud)