您的怀疑完全有道理。您问这个问题已经有几年了,但我认为值得对现有答案添加一些想法。
并行运行一组函数,无需等到前一个函数完成。如果任何函数将错误传递给其回调...
这句话并不完全正确。事实上,它确实会等待每个函数完成,因为在 JavaScript 中不可能不这样做。函数调用和函数返回都是同步和阻塞的。因此,当它调用任何函数时,它必须等待它返回。它不必等待的是调用传递给该函数的回调。
寓言
前段时间我写了一篇短篇小说来证明这个概念:
引用其中的一部分:
“所以我说:‘等一下,你告诉我一个蛋糕需要三个半小时,而四个蛋糕只需要一个多半小时?这没有任何意义!’我想她一定是在开玩笑,所以我开始笑。”
“但她不是在开玩笑吗?”
“不,她看着我说:‘这完全有道理。这个时间主要是在等待。我可以一次等待很多事情。我停止了笑,开始思考。它终于开始影响我了。同时做四个枕头并没有给你带来任何时间,也许可以说它更容易组织,但话又说回来,也许不是。但这一次,情况有所不同。但我还不知道如何使用这些知识。”
理论
我认为需要强调的是,在单线程事件循环中,您一次只能做一件事情。但是你可以一次等待很多事情就好了。这就是这里发生的事情。
Async 模块中的并行函数一个一个地调用每个函数,但是每个函数都必须在调用下一个函数之前返回,没有办法绕过它。这里的神奇之处在于该函数在返回之前并没有真正完成它的工作 - 它只是安排一些任务,注册一个事件监听器,在其他地方传递一些回调,向一些承诺添加一个解析处理程序等。
然后,当计划任务完成时,执行该函数先前注册的某个处理程序,这将依次执行最初由 Async 模块传递的回调,并且 Async 模块知道该函数已完成 - this时间不仅在某种意义上它返回了,而且传递给它的回调最终被调用了。
示例
例如,假设您有 3 个函数下载 3 个不同的 URL:getA()、getB() 和 getC()。
我们将编写一个模拟 Request 模块来模拟请求和一些延迟:
function mockRequest(url, cb) {
const delays = { A: 4000, B: 2000, C: 1000 };
setTimeout(() => {
cb(null, {}, 'Response ' + url);
}, delays[url]);
};
现在这 3 个函数基本相同,带有详细的日志记录:
function getA(cb) {
console.log('getA called');
const url = 'A';
console.log('getA runs request');
mockRequest(url, (err, res, body) => {
console.log('getA calling callback');
cb(err, body);
});
console.log('getA request returned');
console.log('getA returns');
}
function getB(cb) {
console.log('getB called');
const url = 'B';
console.log('getB runs request');
mockRequest(url, (err, res, body) => {
console.log('getB calling callback');
cb(err, body);
});
console.log('getB request returned');
console.log('getB returns');
}
function getC(cb) {
console.log('getC called');
const url = 'C';
console.log('getC runs request');
mockRequest(url, (err, res, body) => {
console.log('getC calling callback');
cb(err, body);
});
console.log('getC request returned');
console.log('getC returns');
}
最后我们使用async.parallel 函数调用它们:
async.parallel([getA, getB, getC], (err, results) => {
console.log('async.parallel callback called');
if (err) {
console.log('async.parallel error:', err);
} else {
console.log('async.parallel results:', JSON.stringify(results));
}
});
立即显示的是:
getA called
getA runs request
getA request returned
getA returns
getB called
getB runs request
getB request returned
getB returns
getC called
getC runs request
getC request returned
getC returns
正如你所看到的,这都是顺序的——函数被一个一个地调用,下一个函数在前一个函数返回之前不会被调用。然后我们会看到一些延迟:
getC calling callback
getB calling callback
getA calling callback
async.parallel callback called
async.parallel results: ["Response A","Response B","Response C"]
所以getC 首先完成,然后是getB 和getC - 然后在最后一个完成后,async.parallel 调用我们的回调,所有响应都以正确的顺序组合在一起 - 在按我们订购函数的顺序,而不是按照这些请求完成的顺序。
我们还可以看到程序在 4.071 秒后结束,这大约是最长请求所用的时间,因此我们看到所有请求都在同一时间进行。
现在,让我们使用async.parallelLimit 运行它,最多限制 2 个并行任务:
async.parallelLimit([getA, getB, getC], 2, (err, results) => {
console.log('async.parallel callback called');
if (err) {
console.log('async.parallel error:', err);
} else {
console.log('async.parallel results:', JSON.stringify(results));
}
});
现在有点不同了。我们立即看到的是:
getA called
getA runs request
getA request returned
getA returns
getB called
getB runs request
getB request returned
getB returns
所以getA 和getB 被调用并返回,但getC 根本没有被调用。然后经过一段时间的延迟,我们看到:
getB calling callback
getC called
getC runs request
getC request returned
getC returns
这表明,一旦getB 调用回调,Async 模块就不再有 2 个任务正在进行,而只有 1 个任务可以启动另一个任务,即getC,它会立即执行。
然后我们看到另一个延迟:
getC calling callback
getA calling callback
async.parallel callback called
async.parallel results: ["Response A","Response B","Response C"]
就像在async.parallel 示例中一样完成整个过程。这次整个过程也花了大约 4 秒,因为延迟调用 getC 没有任何区别 - 它仍然在第一次调用 getA 完成之前完成。
但如果我们将延迟更改为这些延迟:
const delays = { A: 4000, B: 2000, C: 3000 };
那么情况就不同了。现在async.parrallel 需要 4 秒,但限制为 2 的async.parallelLimit 需要 5 秒,并且顺序略有不同。
没有限制:
$ time node example.js
getA called
getA runs request
getA request returned
getA returns
getB called
getB runs request
getB request returned
getB returns
getC called
getC runs request
getC request returned
getC returns
getB calling callback
getC calling callback
getA calling callback
async.parallel callback called
async.parallel results: ["Response A","Response B","Response C"]
real 0m4.075s
user 0m0.070s
sys 0m0.009s
限制为 2:
$ time node example.js
getA called
getA runs request
getA request returned
getA returns
getB called
getB runs request
getB request returned
getB returns
getB calling callback
getC called
getC runs request
getC request returned
getC returns
getA calling callback
getC calling callback
async.parallel callback called
async.parallel results: ["Response A","Response B","Response C"]
real 0m5.075s
user 0m0.057s
sys 0m0.018s
总结
我认为要记住的最重要的事情 - 无论您使用这种情况下的回调,还是使用 Promise 或 async/await,在单线程事件循环中您一次只能做一件事,但您可以同时等待很多事情。