node 的事件驱动编程模型.js使协调程序流变得有些棘手.
简单的顺序执行会转化为嵌套回调,这很容易(尽管写下来有点复杂).
但是并行执行呢?假设你有三个任务A、B、C可以并行运行,当它们完成后,你想把它们的结果发送给任务D.
对于fork/join模型,这将是
- fork
- fork B
- fork C
- 加入A、B、C、D
我该如何在node中写呢.js?有什么最佳实践或食谱吗?我是不是每次都得打hand-roll a solution分,还是有图书馆有帮手?
node 的事件驱动编程模型.js使协调程序流变得有些棘手.
简单的顺序执行会转化为嵌套回调,这很容易(尽管写下来有点复杂).
但是并行执行呢?假设你有三个任务A、B、C可以并行运行,当它们完成后,你想把它们的结果发送给任务D.
对于fork/join模型,这将是
我该如何在node中写呢.js?有什么最佳实践或食谱吗?我是不是每次都得打hand-roll a solution分,还是有图书馆有帮手?
在 node 中没有什么是真正平行的.js,因为它是单线程的.但是,可以安排多个事件,并按事先无法确定的顺序运行.数据库访问之类的事情实际上是"并行"的,因为数据库查询本身在单独的线程中运行,但完成后会重新集成到事件流中.
那么,如何在多个事件处理程序上安排回调?这是浏览器端javascript动画中使用的一种常见技术:使用变量跟踪完成情况.
这听起来像是一个黑客攻击,而且听起来可能很混乱,留下一堆全局变量来进行跟踪,用一种不太常用的语言.但在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);
}
}
// usage:
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 = [];
// we use the arguments object here because some callbacks
// in Node pass in multiple arguments as result.
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:
// Read 3 files in parallel and process them together:
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];
// process the files together here
}
fork([A,B,C],D);
这段代码是在async等库出现之前编写的.js或各种基于promise的库.我愿意相信异步.js的灵感来源于此,但我没有任何证据.无论如何如果你今天想这么做,看看async.或promise .只要考虑上面的答案,就可以很好地解释和说明事物是什么样子的.并行工作.
为了完整性起见,以下是使用async.parallel
的方法:
var async = require('async');
async.parallel([A,B,C],D);
请注意,async.parallel
的工作原理与我们上面实现的fork
函数完全相同.主要区别在于,它将错误作为第一个参数传递给D
,并根据 node 将回调作为第二个参数.js公约.
使用promise ,我们将写如下:
// Assuming A, B & C return a promise instead of accepting a callback
Promise.all([A,B,C]).then(D);