Node.js模块的asynchronous初始化

我想以asynchronous的方式初始化模块,并提出一些想法。 我需要数据库对象与从Mongo和其他数据的集合列表,但./中的文件列表将简洁起来。

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


首先也是最简单的是,将module.exports分配给Object并在以后填充它:

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

不好的事情 – 我从外面不清楚列表是否准备好,没有好的办法来检查错误。


EventEmitter 第二种方法是inheritanceEventEmitter并通知所有人数据库已准备好或发生错误。 如果一切正常 – 继续前进。

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

我应该select什么?为什么? 一般来说这样的想法有多糟糕,特别是我的select?

感谢您的反馈。

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

详细说明:

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

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

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

永远不能采取一个asynchronous的一段代码,并为它创build一个同步API。

require使用一个名为readFileSyncreadFile的特殊同步版本。 由于模块实际上只是在程序开始时加载的,所以在读取模块时阻止node.js的执行并不是问题。

在你的例子中,你尝试在require阶段执行额外的asynchronousI / Oreaddir() 。 因此,您需要使用此命令的同步版本或API需要更改…

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

您确定了两个基本选项:

  1. 使用承诺 (这与您的EventEmitter示例基本相同)
  2. 使用callback (你的第二个例子显示了这一点),第三个是:
  3. 使用名为readdirSync()readdir()命令的同步版本

为简单起见,我会使用选项#3 ,但前提是您打算在启动时只读几个文件,就像您的示例所示。 如果以后你的数据库模块实际上要连接到一个数据库 – 或者如果你打算在运行时做任何这样的事情,那么现在就跳船,并使用asynchronousAPI。

没有多less人记得这一点,但承诺实际上是如何在node.js中处理asynchronous的原始默认值。 然而,在节点0.1.30中,promisses已经被删除,并且被标准的callbackfunction(err, result)签名所代替 。 这主要是出于简单的原因。

现在,绝大多数asynchronous调用都将此标准callback作为最后一个参数。 你的数据库驱动程序做到这一点,你的web框架就是这样 – 它无处不在。 你应该保持stream行的devise,并使用它。

偏好承诺或事件的唯一原因是如果你有多个不同的结果可能发生。 例如,一个套接字可以打开,接收数据,closures,刷新等。

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

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

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

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

asynchronous选项:
当你想使用数据库等

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

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

编辑:刚刚意识到,这是从你的最后一个例子的方法。我的投票)

模块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 } 

在我这边这样的模块是一个函数,需要callback(如果在内部configuration的承诺也返回承诺(见https://github.com/medikoo/deferred ));

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