我想以异步方式初始化模块并提出一些想法。我需要包含来自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!
});
我应该选择什么?为什么?这个想法总的来说有多糟糕,特别是我的选择?
感谢您的反馈。
答案 0 :(得分:26)
TL; DR:如果您只是计划在启动时读取本地文件,请使用readdirSync()
代替readdir()
。如果您计划实际从远程数据库读取数据或在运行时执行任何I / O,请使用选项#2 - 回调。下面的解释和代码示例。
详细说明:
虽然起初这看起来像是一个模块/依赖/需求相关的问题,但事实并非如此。这是如何处理异步代码的一般性问题。让我解释一下:
require()
基本上是唯一在整个节点中广泛使用的同步函数,它处理I / O(它需要来自文件系统的其他模块)。同步意味着它实际上将其数据作为返回值返回,而不是调用回调。
异步编程中最基本的101规则是:
您可以从不获取异步代码并为其创建同步API。
require
使用名为readFile
的{{1}}特殊同步版本。由于模块实际上只是在程序开始时加载,因此它在读取模块时阻止node.js执行的事实不是问题。
但是,在您的示例中,您尝试在require阶段执行其他异步I / O - readFileSync
。因此,您需要使用此命令的同步版本或API需要更改...
所以你的问题有背景。
您确定了两个基本选项:
readdir()
示例基本相同)EventEmitter
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中用结果集调用你的回调。