await Promise.all()和multiple await之间有什么区别吗?

这两者之间有什么区别吗:

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];
263605 次浏览

请注意:

这个答案只涵盖了系列中的awaitPromise.all之间的时间差异。请务必阅读@mikep的全面回答还涵盖了错误处理中更重要的差异


为了回答这个问题,我将使用一些示例方法:

  • res(ms)是一个函数,它接受一个毫秒的整数,并返回一个承诺,在这个毫秒之后解析。
  • rej(ms)是一个函数,它接受一个毫秒的整数,并返回一个promise,该promise在这个毫秒之后被拒绝。

调用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} called after ${n} milliseconds`, 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有一个“快速失败”;行为:

例# 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} called after ${n} milliseconds`, Date.now() - start)
}, n)
})
}
  

function rej(n) {
const id = ++i
return new Promise((resolve, reject) => {
setTimeout(() => {
reject()
console.log(`rej #${id} called after ${n} milliseconds`, 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() - start)
}
}


example()

如果你使用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} called after ${n} milliseconds`, Date.now() - start)
}, n)
})
}
  

function rej(n) {
const id = ++i
return new Promise((resolve, reject) => {
setTimeout(() => {
reject()
console.log(`rej #${id} called after ${n} milliseconds`, Date.now() - start)
}, n)
})
}
  

try {
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()

你可以自己查一下。

在这个小提琴中,我运行了一个测试来演示await的阻塞性质,而不是Promise.all,后者将启动所有的promise,当一个promise等待时,它将继续与其他promise一起进行。

第一个区别——快速失败

我同意@zzzzBov的回答,但是“失败快”;Promise.all的优点并不是唯一的区别。一些用户在评论中问,为什么使用Promise.all是值得的,因为它只在负面情况下更快(当某些任务失败时)。我问,为什么不呢?如果我有两个独立的异步并行任务,第一个需要很长时间来解决,但第二个在很短的时间内被拒绝,为什么让用户等待更长的调用来完成接收错误消息?在实际应用中,我们必须考虑消极的情况。但是好吧——在第一个区别中,你可以决定使用哪个替代:Promise.all还是多个await

第二个区别——错误处理

但是当考虑错误处理时,你必须使用Promise.all。不可能正确处理由多个__abc1触发的异步并行任务的错误。在消极的情况下,无论你在哪里使用try/ catch,你总是会以UnhandledPromiseRejectionWarningPromiseRejectionHandledWarning结尾。这就是设计Promise.all的原因。当然,有人会说我们可以使用process.on('unhandledRejection', err => {})process.on('rejectionHandled', err => {})来抑制这些错误,但这不是好的实践。我在互联网上找到了许多例子,它们根本不考虑对两个或多个独立的异步并行任务进行错误处理,或者考虑的方式是错误的——只是使用try/ catch并希望它能捕获错误。在这方面几乎不可能找到好的实践。

总结

TL;DR:永远不要为两个或多个独立的异步并行任务使用多个await,因为你将无法正确处理错误。对于这个用例,总是使用Promise.all()

Async/ await不是promise的替代品,它只是一种使用promise的漂亮方式。异步代码以“同步风格”编写。并且我们可以在承诺中避免多个__abc1。

有些人说,当使用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.all和多个__abc1之间没有区别。这两个例子都在5秒后以Task 1 succeed! Task 2 succeed!结尾。

// 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!

我们应该已经注意到,当并行使用多个__abc时,我们正在做一些错误的事情。让我们试着处理错误:

// 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函数添加一个捕获,并将带有捕获逻辑的代码添加到回调中。我们不需要在run函数内部处理错误,因为异步函数会自动这样做——承诺拒绝task函数会导致拒绝run函数。

为了避免回调,我们可以使用“sync style”;(async/ await + try/ catch)
try { await run(); } catch(err) { }
但在这个例子中,这是不可能的,因为我们不能在主线程中使用await——它只能在异步函数中使用(因为没有人想阻塞主线程)。要测试是否处理工作在“同步风格”;我们可以从另一个async函数调用run函数,或者使用IIFE(立即调用函数表达式:中数):

(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)
< p >…嗯?我们首先看到任务2的错误没有被处理,后来它被捕获了。在主机中仍然充满误导和错误,这种方式仍然不可用。
< br >

(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)
< p >…同上。用户@Qwerty在他删除的回答中询问了这种奇怪的行为,错误似乎被捕捉到,但也没有得到处理。我们捕获错误,因为run()在包含await关键字的行上被拒绝,并且可以在调用run()时使用try/ catch捕获。我们还会得到一个未处理的错误,因为我们正在同步调用一个异步任务函数(没有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)

... “only"两个错误(第三个错误丢失),但没有捕获任何错误。

附加(处理独立任务错误和首次失败错误)

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用于触发最终错误)。

等待Promise.all([task1(), task2()]);的情况下,“task1()”和“task2()”将并行运行,并将等待两个承诺都完成(解决或拒绝)。而在

const result1 = await t1;
const result2 = await t2;

T2将只在t1完成执行(已被解析或拒绝)后运行。t1和t2都不是平行的。

通常,使用Promise.all()运行请求"异步"并行执行。使用await可以并行运行被“同步”;阻塞。

下面的test1test2函数显示了await如何运行异步或同步。

test3显示了异步的Promise.all()

Jsfiddle定时结果 -打开浏览器控制台查看测试结果

同步行为。不并行运行,需要~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(),它也会“快速失效”;-在任何包含函数的第一个失败时停止运行。