由于Node.js是单线程的,因此在它内部不存在真正的并行。但是,可以安排多个事件以一个无法预先确定的顺序运行。有些事情,比如数据库访问,实际上是“并行”的,因为数据库查询本身在单独的线程中运行,但在完成后会重新集成到事件流中。
那么,如何在多个事件处理程序上安排回调函数呢?嗯,在浏览器端JavaScript动画中,这是一种常见的技术:使用变量来跟踪完成情况。
这听起来像是一种hack,而且它确实是。在其他不那么好的语言中,可能会留下一堆全局变量进行跟踪,并且可能会变得非常混乱。但是,在JavaScript中,我们可以使用闭包:
function fork (async_calls, shared_callback) {
var counter = async_calls.length;
var callback = function () {
counter --;
if (counter == 0) {
shared_callback()
}
}
for (var i=0;i<async_calls.length;i++) {
async_calls[i](callback);
}
}
fork([A,B,C],D);
在上面的示例中,我们假设异步和回调函数不需要任何参数来保持代码简单。当然,您可以修改代码以向异步函数传递参数,并使回调函数累积结果并将其传递给共享回调函数。
额外的答案:
实际上,即使现在这样,fork()
函数已经可以使用闭包向异步函数传递参数:
fork([
function(callback){ A(1,2,callback) },
function(callback){ B(1,callback) },
function(callback){ C(1,2,callback) }
],D);
现在唯一要做的就是累加来自A、B、C的结果并将它们传递给D。
更多额外的答案:
我忍不住了。早餐期间一直在想这个问题。这里是一个实现 fork()
并累加结果(通常作为回调函数的参数传递)的示例:
function fork (async_calls, shared_callback) {
var counter = async_calls.length;
var all_results = [];
function makeCallback (index) {
return function () {
counter --;
var results = [];
for (var i=0;i<arguments.length;i++) {
results.push(arguments[i]);
}
all_results[index] = results;
if (counter == 0) {
shared_callback(all_results);
}
}
}
for (var i=0;i<async_calls.length;i++) {
async_calls[i](makeCallback(i));
}
}
这相当容易。这使得fork()
相当通用,可用于同步多个非同构事件。
在Node.js中的示例用法:
function A (c){ fs.readFile('file1',c) };
function B (c){ fs.readFile('file2',c) };
function C (c){ fs.readFile('file3',c) };
function D (result) {
file1data = result[0][1];
file2data = result[1][1];
file3data = result[2][1];
}
fork([A,B,C],D);
更新
这段代码是在像async.js或诸如基于Promise的库之类的库出现之前编写的。我想相信async.js受到了这个代码的启发,但我没有任何证据可以证明这一点。无论如何...如果你今天想要做这个,请看看async.js或promises。只需将上面的答案视为说明/演示异步操作如何工作的好方法。
为了完整起见,以下是使用async.parallel
的方法:
var async = require('async');
async.parallel([A,B,C],D);
请注意,
async.parallel
的工作方式与我们上面实现的
fork
函数完全相同。主要区别在于它将错误作为第一个参数传递给
D
,将回调函数作为第二个参数按照 Node.js 约定传递。
使用 promises,我们可以编写如下代码:
// Assuming A, B & C return a promise instead of accepting a callback
Promise.all([A,B,C]).then(D);