22

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

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


首先,我想到的最简单的方法是稍后分配module.exportsObject填充它:

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

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

感谢您的反馈。

4

3 回答 3

26

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

详细解释:

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

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

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

永远无法获取异步代码并为其创建同步 API。

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

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

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

您确定了两个基本选项:

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

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

已经没有多少人记得这一点了,但 Promise 实际上是如何在 node.js 中处理异步的原始默认设置。然而,在节点 0.1.30 中,promises 被删除并由带有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

异步选项:
当您想使用 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
});
于 2012-08-25T21:15:50.970 回答
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
}
于 2012-08-06T23:02:33.593 回答
1

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

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

于 2012-08-07T08:41:32.230 回答