在 Node.JS 中有一些不同的方式来进行控制流。 “传统”最先进的做法是使用回调,但这会导致我们称之为“回调地狱”或回旋镖效应。您还可以使用 promises 或 库来处理回调地狱。在 Node.JS 中进行控制流(以及其他)的另一种方法是使用流。
有关不同控制流技术的示例,请参见下文。
使用回调的示例:
function prep1(x, cb) { do something; cb(); };
function prep2(x, cb) { do something; cb(); };
function prep3(x, cb) { do something; cb(); };
function mainLoop(x) { };
prep1(x, function () {
prep2(y, function () {
prep3(z, function () {
mainLoop();
});
});
});
// could be shortened to
prep1(x, prep2(y, prep3(z, mainLoop)));
如您所见,这可能会导致一些非常丑陋的代码。另外,请注意,此代码不是并行运行,而是串行运行。如果您想在每个准备函数中都有某种异步操作的情况下并行运行它,请参见下文以了解这将如何工作。
使用并行回调的示例:
// cb() is called inside some async operation
function prep1(x, cb) { do async; cb(y); };
function prep2(x, cb) { do async; cb(y); };
function prep3(x, cb) { do async; cb(y); };
function mainLoop(x) {
// x is an array of values given by the callback in prep1-3
};
var numCalls = 0, result = [];
function maybeCallMainLoop(data) {
if (++numCalls !== 3) {
// Accumulate all responses given by argument y from prep1-3
result.push(data);
return;
}
// run main loop when all functions are called
mainLoop(result);
}
prep1(x, maybeCallMainLoop);
prep2(y, maybeCallMainLoop);
prep3(z, maybeCallMainLoop);
这并不是最干净、最可靠的方法。更好的方法是下一个示例。
使用async library 是解决此问题的一种更简洁的方法。更具体地说,#parallel method
使用#parallel method 的async library 的示例
// Signature of the callback: callback(error, value)
function prep1(x, cb) { do something; cb(null, "value"); };
function prep2(x, cb) { do something; cb(null, "value"); };
function prep3(x, cb) { do something; cb(null, "value"); };
function mainLoop(error, x) { };
async.parallel([
prep1,
prep2,
prep3
], mainLoop);
在the async readme中查看更多信息
正如您所说,第三种方法是使用 Promise。您将从每个准备函数返回一个承诺,然后等待执行 mainLoop。
您可以使用Q library,它是一个符合 A* 的承诺库。
通过Q library 使用承诺的示例
var Q = require('q');
// Signature of the callback: callback(error, value)
function prep1(x) {
// do something;
var deferred = Q.defer();
deferred.resolve() // resolve promise
return deferred.promise;
};
function prep2(x) { // same as prep1 };
function prep3(x) { // same as prep1 };
function mainLoop(result) { };
// Q.all returns a promise that is
// resolved when all promises given are resolved
Q.all([
prep1(x),
prep2(y),
prep3(z)
]).then(mainLoop);
见documentation of Q.all
这是解决它的一些方法。我并没有真正看到使用事件以一种漂亮的方式解决这个问题的方法——但它看起来很像带有并行回调的示例。带有一个标志,指示已完成的准备功能的数量。
您可以看到,使用 async 或 Promise 之类的东西很容易成为最容易理解和易读的东西。如果您只想获得“瘦”的结果(cyclomatic complexity 较低的函数),我可能会建议使用async。如果您的准备工作说明了某些复杂性和/或结果如何以不同的方式使用,那么 Promise 将是最好的方法。
关于流的小结
在 Node.JS 中,你也有一个叫做 streams 的东西。以最基本的方式,流是可管理的事件发射器,具有背压(一种停止数据流的方式)功能。阅读Daddy, what's a stream? 中的一个简单类比。
使用流,您可以拥有一个源并将其通过不同的转换流进行管道传输,并以某种接收器或端点结束流。这就是串行而不是并行的方式,但是您可以使用流做更多的事情。 Streams 与 Promise 有一些共同点。
使用流的示例
function prep1(x) { return stream; };
function prep2(x) { return stream; };
function prep3(x) { return stream; };
function mainLoop(result) { };
// Some start. E.g fs.createReadStream("somefile")
someStartStream
.pipe(prep1(x))
.pipe(prep2(y))
.pipe(prep3(z))
.on('end', mainLoop);
同样,这是按顺序进行的,而不是并行的。这只是为了表明在 Node.JS 的控制流中你也可以使用流。
祝你好运!