第一个区别 - 快速失败
我同意@zzzzBov的答案,但Promise.all
的“快速失败”优势并不是唯一的区别。在评论中,一些用户问为什么在负面情况下(当某些任务失败时)使用Promise.all
要比其他方式值得。而我想问:为什么不呢?如果我有两个独立的异步并行任务,第一个任务需要很长时间才能完成,而第二个任务在很短的时间内被拒绝了,为什么要让用户等待更长时间的调用才能收到错误信息?在现实生活中的应用程序中,我们必须考虑负面情况。但好吧,在这个第一个区别中,您可以决定使用哪种替代方案:Promise.all
还是多个await
。
第二个区别 - 错误处理
但在考虑错误处理时,必须使用Promise.all
。使用多个await
触发的异步并行任务无法正确处理错误。在负面情况下,无论您在哪里使用try/catch,您都将以UnhandledPromiseRejectionWarning
和PromiseRejectionHandledWarning
结束。这就是为什么会设计Promise.all
的原因。当然,有人可能会说,我们可以使用process.on('unhandledRejection', err => {})
和process.on('rejectionHandled', err => {})
来抑制这些错误,但这不是好的做法。我在互联网上找到了许多根本没有考虑两个或更多独立异步并行任务的错误处理,或者考虑了但以错误的方式处理错误 - 只使用try/catch并希望它能捕获错误。几乎不可能找到这方面的良好实践。
摘要
TL;DR: 不要为两个或更多独立异步并行任务使用多个await
,因为您将无法正确处理错误。对于这种情况,请始终使用Promise.all()
。
Async/await
不能替代Promises,它只是一种使用promises的漂亮方式。异步代码以“同步样式”编写,我们可以避免在Promises中使用多个then
。
有些人说,在使用Promise.all()
时,我们无法单独处理任务错误,而只能处理第一个被拒绝的promise的错误(单独处理对于日志记录等可能会有用)。这不是问题 - 请参见此答案底部的“附加”标题。
示例
考虑以下异步任务......
const task = function(taskNum, seconds, negativeScenario) {
return new Promise((resolve, reject) => {
setTimeout(_ => {
if (negativeScenario)
reject(new Error('Task ' + taskNum + ' failed!'));
else
resolve('Task ' + taskNum + ' succeed!');
}, seconds * 1000)
});
};
当您在正常情况下运行任务时,Promise.all
和多个await
之间没有区别。这两个示例都在5秒后以 Task 1 succeed! Task 2 succeed!
结束。
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 5, false),
task(2, 5, false)
]);
console.log(r1 + ' ' + r2);
};
run();
const run = async function() {
let t1 = task(1, 5, false);
let t2 = task(2, 5, false);
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
run();
然而,当第一个任务花费10秒成功完成,而第二个任务花费5秒但失败时,发出的错误信息也有所不同。
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, false),
task(2, 5, true)
]);
console.log(r1 + ' ' + r2);
};
run();
const run = async function() {
let t1 = task(1, 10, false);
let t2 = task(2, 5, true);
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
run();
我们应该已经注意到,当在并行中使用多个await
时,我们正在做错什么。让我们尝试处理这些错误:
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, false),
task(2, 5, true)
]);
console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Caught error', err); });
如您所见,为了成功地处理错误,我们只需要在run
函数中添加一个catch,并在回调函数中添加具有catch逻辑的代码即可。 我们不需要在run
函数内部处理错误,因为异步函数会自动处理 - task
函数的Promise拒绝会导致run
函数的拒绝。
为了避免回调,我们可以使用“同步风格”(async/await
+ try/catch)
try { await run(); } catch(err) { }
但在这个例子中,这是不可能的,因为我们不能在主线程中使用await
- 它只能在异步函数中使用(因为没有人想阻塞主线程)。 为了测试是否在“同步风格”中起作用,我们可以从另一个异步函数中调用run
函数或使用IIFE(立即调用函数表达式:MDN):
(async function() {
try {
await run();
} catch(err) {
console.log('Caught error', err);
}
})();
这是运行两个或多个异步并行任务并处理错误的唯一正确方法。你应该避免下面的示例。
糟糕的示例
const run = async function() {
let t1 = task(1, 10, false);
let t2 = task(2, 5, true);
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
我们可以尝试以多种方式处理上面代码中的错误...
try { run(); } catch(err) { console.log('Caught error', err); };
...什么都没有被捕获,因为它处理同步代码,但run
是异步的。
run().catch(err => { console.log('Caught error', err); });
... 咦?我们首先看到任务2的错误没有被处理,稍后却被捕捉了。仍然有很多控制台错误信息,这样还是无法使用。
(async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
......与上面相同。用户 @Qwerty 在他删除的答案中询问了这种奇怪的行为,即捕获到一个错误但也未经处理。我们捕获错误是因为 run()
在带有 await
关键字的行上被拒绝,可以在调用 run()
时使用 try/catch 捕获。我们还会出现一个 未处理 的错误,因为我们在同步地调用异步任务函数(没有 await
关键字),并且此任务在 run()
函数之外运行和失败。
这类似于当我们无法通过 try/catch 处理调用某些同步函数调用 setTimeout 时的错误:
function test() {
setTimeout(function() {
console.log(causesError);
}, 0);
};
try {
test();
} catch(e) {
}`.
另一个糟糕的例子:
const run = async function() {
try {
let t1 = task(1, 10, false);
let t2 = task(2, 5, true);
let r1 = await t1;
let r2 = await t2;
}
catch (err) {
return new Error(err);
}
console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Caught error', err); });
......仅有两个错误(第三个错误缺失),但是没有检测出来。
添加(处理单独任务错误和首次失败错误)
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, true).catch(err => { console.log('Task 1 failed!'); throw err; }),
task(2, 5, true).catch(err => { console.log('Task 2 failed!'); throw err; })
]);
console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Run failed (does not matter which task)!'); });
请注意,在这个例子中,我拒绝了两个任务,以更好地说明发生了什么(throw err
用于触发最终错误)。