你的怀疑完全有道理。您问这个问题已经有几年了,但我认为值得对现有答案添加一些想法。
并行运行一组函数,无需等到前一个函数完成。如果任何函数将错误传递给其回调...
这句话并不完全正确。事实上,它确实会等待每个函数完成,因为在 JavaScript 中不可能不这样做。函数调用和函数返回都是同步和阻塞的。因此,当它调用任何函数时,它必须等待它返回。它不必等待的是调用传递给该函数的回调。
寓言
前段时间我写了一个短篇故事来证明这个概念:
引用其中的一部分:
“所以我说:‘等一下,你告诉我一个蛋糕需要三个半小时,而四个蛋糕只需要一个多半小时?这没有任何意义!我想她一定是在开玩笑,所以我开始大笑。”
“但她不是在开玩笑吗?”
“不,她看着我说:‘这完全有道理。这个时间主要是在等待。我可以一次等待很多事情就好了。我停止了笑,开始思考。它终于开始影响我了。同时做四个枕头并没有给你带来任何时间,也许可以说它更容易组织,但话又说回来,也许不是。但这一次,情况有所不同。但我还不知道如何使用这些知识。”</p>
理论
我认为需要强调的是,在单线程事件循环中,你一次只能做一件事情。但是你可以一次等待很多事情就好了。这就是这里发生的事情。
Async 模块中的并行函数一个一个地调用每个函数,但是每个函数都必须在调用下一个函数之前返回,没有办法绕过它。这里的神奇之处在于该函数在返回之前并没有真正完成它的工作 - 它只是安排一些任务,注册一个事件监听器,在其他地方传递一些回调,向一些承诺添加一个解析处理程序等。
然后,当计划任务完成时,执行该函数先前注册的某个处理程序,这将依次执行最初由 Async 模块传递的回调,并且 Async 模块知道该函数已完成 - 这次不仅从某种意义上说,它返回了,而且传递给它的回调最终被调用了。
例子
因此,例如,假设您有 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
andgetB
被调用并返回,但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 秒 async.parallelLimit
,但限制为 2 需要 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,在单线程事件循环中您一次只能做一件事,但您可以等待很多同时的事情。