有什么区别:
const [result1, result2] = await Promise.all([task1(), task2()]);
和
const t1 = task1();
const t2 = task2();
const result1 = await t1;
const result2 = await t2;
和
const [t1, t2] = [task1(), task2()];
const [result1, result2] = [await t1, await t2];
注意:这个答案只涵盖了 await in series 和 Promise.all 之间的时间差异。请务必阅读@mikep 的综合答案,该答案还涵盖了错误处理中更重要的差异。
出于此答案的目的,我将使用一些示例方法:
res(ms) 是一个函数,它需要一个整数毫秒并返回一个承诺,该承诺会在几毫秒后解决。
rej(ms) 是一个函数,它需要一个整数毫秒并返回一个承诺,该承诺会在几毫秒后拒绝。
调用 res
启动计时器。使用 Promise.all
等待一些延迟将在所有延迟完成后解决,但请记住它们同时执行:
示例 #1
const data = await Promise.all([res(3000), res(2000), res(1000)])
// ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^
// delay 1 delay 2 delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O delay 2
// =========O delay 3
//
// =============================O Promise.all
async function example() { const start = Date.now() let i = 0 function res(n) { const id = ++i return new Promise((resolve, reject) => { setTimeout(() => { resolve () console.log(`res #${id} 在 ${n} 毫秒后调用`, Date.now() - start) }, n) }) } const data = await Promise.all([res(3000) , res(2000), res(1000)]) console.log(`Promise.all finished`, Date.now() - start) } example()
这意味着 Promise.all
将在 3 秒后使用来自内部 Promise 的数据进行解析。
但是,Promise.all
has a "fail fast" behavior:
示例 #2
const data = await Promise.all([res(3000), res(2000), rej(1000)])
// ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^
// delay 1 delay 2 delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O delay 2
// =========X delay 3
//
// =========X Promise.all
async function example() { const start = Date.now() let i = 0 function res(n) { const id = ++i return new Promise((resolve, reject) => { setTimeout(() => { resolve () console.log(`res #${id} 在 ${n} 毫秒后调用`, Date.now() - start) }, n) }) } function rej(n) { const id = ++i return new Promise((resolve, reject) => { setTimeout(() => { reject() console.log(`rej #${id} 在 ${n} 毫秒后调用`, Date.now() - start) } , n) }) } try { const data = await Promise.all([res(3000), res(2000), rej(1000)]) } catch (error) { console.log(`Promise.all finished`, Date.now() - 开始) } } 例子()
如果您改用 async-await
,则必须等待每个 Promise 依次解析,这可能效率不高:
示例#3
const delay1 = res(3000)
const delay2 = res(2000)
const delay3 = rej(1000)
const data1 = await delay1
const data2 = await delay2
const data3 = await delay3
// ms ------1---------2---------3
// =============================O delay 1
// ===================O delay 2
// =========X delay 3
//
// =============================X await
async function example() { const start = Date.now() let i = 0 function res(n) { const id = ++i return new Promise((resolve, reject) => { setTimeout(() => { resolve () console.log(`res #${id} 在 ${n} 毫秒后调用`, Date.now() - start) }, n) }) } function rej(n) { const id = ++i return new Promise((resolve, reject) => { setTimeout(() => { reject() console.log(`rej #${id} 在 ${n} 毫秒后调用`, Date.now() - start) } , n) }) } 尝试 { const delay1 = res(3000) const delay2 = res(2000) const delay3 = rej(1000) const data1 = await delay1 const data2 = await delay2 const data3 = await delay3 } catch (error) { console.log(`await finished`, Date.now() - start) } } example()
第一个区别 - 快速失败
我同意@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 的替代品,它只是一种使用 Promise 的好方法。异步代码以“同步风格”编写,我们可以避免在 Promise 中使用多个 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!
结尾。
// Promise.all alternative
const run = async function() {
// tasks run immediate 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 immediate 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!
我们应该已经注意到,在并行使用多个 await
时我们做错了。让我们尝试处理错误:
// 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!
如您所见,要成功处理错误,我们只需向 run
函数添加一个 catch 并将具有 catch 逻辑的代码添加到回调中。我们不需要处理 run
函数内部的错误,因为异步函数会自动执行此操作 - 承诺拒绝 task
函数会导致拒绝 run
函数。
为了避免回调,我们可以使用“同步样式”(async/ await
+ try/catch)
try { await run(); } catch(err) { }
但在这个例子中这是不可能的,因为我们不能在主线程 - 它只能用于异步函数(因为没有人想阻塞主线程)。要测试处理是否以“同步风格”工作,我们可以从另一个异步函数调用 run
函数或使用 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
是异步的。
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)
……同上。用户@Qwerty 在他删除的答案中询问了这种奇怪的行为,其中错误似乎被捕获但也未处理。我们捕获错误,因为 run()
在使用 await
关键字的行被拒绝,并且可以在调用 run()
时使用 try/catch 捕获。我们还会收到 unhandled 错误,因为我们正在同步调用异步任务函数(没有 await
关键字),并且此任务在 run()
函数之外运行并失败。
类似当我们在调用某些调用 setTimeout 的同步函数时无法通过 try/catch 处理错误时:
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)
...“仅”两个错误(缺少第 3 个错误)但没有发现任何错误。
加法(处理单独的任务错误和首次失败错误)
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
用于触发最终错误)。
try { [await rejected1, await rejected2] } catch(err) { console.log(err) }
将捕获第一个失败的承诺(从左到右),因为我们按顺序打开然后,如果需要(按顺序)等待。它不会导致 UnhandledPromiseRejectedWarning
。
通常,使用 Promise.all()
会并行运行“异步”请求。使用 await
可以并行运行或被“同步”阻塞。
下面的 test1 和 test2 函数显示了 await
如何运行异步或同步。
test3 显示异步的 Promise.all()
。
jsfiddle with timed results - 打开浏览器控制台查看测试结果
同步行为。不并行运行,大约需要 1800 毫秒:
const test1 = async () => {
const delay1 = await Promise.delay(600); //runs 1st
const delay2 = await Promise.delay(600); //waits 600 for delay1 to run
const delay3 = await Promise.delay(600); //waits 600 more for delay2 to run
};
异步行为。并行运行,大约需要 600 毫秒:
const test2 = async () => {
const delay1 = Promise.delay(600);
const delay2 = Promise.delay(600);
const delay3 = Promise.delay(600);
const data1 = await delay1;
const data2 = await delay2;
const data3 = await delay3; //runs all delays simultaneously
}
异步行为。并行运行,大约需要 600 毫秒:
const test3 = async () => {
await Promise.all([
Promise.delay(600),
Promise.delay(600),
Promise.delay(600)]); //runs all delays simultaneously
};
TLDR;如果您使用 Promise.all()
,它也会“快速失败” - 在任何包含的函数第一次失败时停止运行。
你可以自己检查。
在这个 fiddle 中,我运行了一个测试来演示 await
的阻塞特性,而不是 Promise.all
,它会启动所有的 Promise,当一个等待时它会继续其他的。
t1 = task1(); t2 = task2()
和 then 对他们两个 result1 = await t1; result2 = await t2;
使用 await
就像他的问题一样,而不是你正在测试的使用 await
像 result1 = await task1(); result2 = await task2();
这样的原始调用。他问题中的代码确实立即启动了所有承诺。正如答案所示,不同之处在于使用 Promise.all
方式会更快地报告失败。
如果是 await Promise.all([task1(), task2()]); “task1()”和“task2()”将并行运行,并等待两个promise完成(解决或拒绝)。而如果
const result1 = await t1;
const result2 = await t2;
t2 只会在 t1 完成执行(已解决或拒绝)后运行。 t1 和 t2 都不会并行运行。
Promise.all()
会比背靠背做 3 个await
更快,对吗?因此,出于性能原因执行Promise.all()
是没有用的,除了可能用于快速失败的情况?await
(即data1 = await thing1(); data2 = await thing2(); data3 = await thing3();
),认为他们正在并行运行 Promise。因此,为了回答您的问题,如果您的承诺已经开始,则无法更快地解决问题。我不知道您为什么会认为它们可以通过Promise.all()
以某种方式加速。unhandledrejection
错误。你永远不会想要使用它。请将此添加到您的答案中。