第一个区别——快速失败
I agree with @zzzzBov's answer, but the "fail fast" advantage of Promise.all is not the only difference. Some users in the comments have asked why using Promise.all is worth it when it's only faster in the negative scenario (when some task fails). And I ask, why not? If I have two independent async parallel tasks and the first one takes a very long time to resolve but the second is rejected in a very short time, why leave the user to wait for the longer call to finish to receive an error message? In real-life applications we must consider the negative scenario. But OK - in this first difference you can decide which alternative to use: Promise.all vs. multiple await.
第二个区别——错误处理
But when considering error handling, YOU MUST use Promise.all. It is not possible to correctly handle errors of async parallel tasks triggered with multiple awaits. In the negative scenario, you will always end with UnhandledPromiseRejectionWarning and PromiseRejectionHandledWarning, regardless of where you use try/ catch. That is why Promise.all was designed. Of course someone could say that we can suppress those errors using process.on('unhandledRejection', err => {}) and process.on('rejectionHandled', err => {}) but this is not good practice. I've found many examples on the internet that do not consider error handling for two or more independent async parallel tasks at all, or consider it but in the wrong way - just using try/ catch and hoping it will catch errors. It's almost impossible to find good practice in this.
总结
TL;DR:永远不要对两个或多个独立的异步并行任务使用多个await,因为您将无法正确处理错误。对于这个用例,总是使用Promise.all()。
Async/ await不是promise的替代品,它只是一种使用promise的漂亮方式。异步代码是用“同步风格”编写的,我们可以避免在承诺中使用多个then。
有些人说,当使用promise .all()时,我们不能单独处理任务错误,我们只能处理第一个被拒绝的承诺的错误(单独处理可以很有用,例如用于日志记录)。这不是问题——请参阅答案底部的“加法”标题。
例子
考虑这个异步任务…
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和Promise之间没有区别。所有和多个等待。两个例子都以Task 1 succeed!任务2成功!5秒钟后。
// Promise.all alternative
const run = async function() {
// tasks run immediately in parallel and wait for both results
let [r1, r2] = await Promise.all([
task(1, 5, false),
task(2, 5, false)
]);
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: Task 1 succeed! Task 2 succeed!
// multiple await alternative
const run = async function() {
// tasks run immediately in parallel
let t1 = task(1, 5, false);
let t2 = task(2, 5, false);
// wait for both results
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: Task 1 succeed! Task 2 succeed!
但是,当第一个任务花了10秒并成功,第二个任务花了5秒但失败时,发出的错误是不同的。
// Promise.all alternative
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, false),
task(2, 5, true)
]);
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// multiple await alternative
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();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
// at 10th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
我们应该已经注意到,当并行使用多个等待时,我们正在做一些错误的事情。让我们试着处理错误:
// Promise.all alternative
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); });
// at 5th sec: Caught error Error: Task 2 failed!
如您所见,要成功地处理错误,我们只需要向运行函数添加一个捕获,并将带有捕获逻辑的代码添加到回调中。我们不需要在运行函数内部处理错误,因为异步函数会自动执行此操作——承诺拒绝任务函数会导致拒绝运行函数。
为了避免回调,我们可以使用“sync style”(async/ await + try/ catch)
尝试{等待运行();} catch(err) {}
但在这个例子中,这是不可能的,因为我们不能在主线程中使用await -它只能在异步函数中使用(因为没有人想阻塞主线程)。为了测试处理是否以“同步风格”工作,我们可以从另一个异步函数调用运行函数或使用IIFE(立即调用函数表达式:MDN):
(async function() {
try {
await run();
} catch(err) {
console.log('Caught error', err);
}
})();
这是运行两个或多个异步并行任务并处理错误的唯一正确方法。你应该避免使用下面的例子。
不好的例子
// multiple await alternative
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); };
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled
…什么都没有被捕获,因为它处理同步代码,但运行是异步的。
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: Caught error Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
... 嗯?我们首先看到任务2的错误没有被处理,后来它被捕获了。在主机中仍然充满误导和错误,这种方式仍然不可用。
(async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: Caught error Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
... the same as above. User @Qwerty in his deleted answer asked about this strange behaviour where an error seems to be caught but are also unhandled. We catch error the because run() is rejected on the line with the await keyword and can be caught using try/ catch when calling run(). We also get an unhandled error because we are calling an async task function synchronously (without the await keyword), and this task runs and fails outside the run() function.
It is similar to when we are not able to handle errors by try/ catch when calling some sync function which calls setTimeout:
function test() {
setTimeout(function() {
console.log(causesError);
}, 0);
};
try {
test();
} catch(e) {
/* this will never catch error */
}`.
另一个糟糕的例子:
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); });
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
... “只有”两个错误(第三个错误是缺失的),但什么都没有捕获。
附加(处理独立任务错误和首次失败错误)
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)!'); });
// at 5th sec: Task 2 failed!
// at 5th sec: Run failed (does not matter which task)!
// at 10th sec: Task 1 failed!
... 请注意,在这个例子中,我拒绝了两个任务,以更好地演示发生了什么(throw err用于触发最终错误)。