admin管理员组文章数量:1129454
Is there any difference between:
const [result1, result2] = await Promise.all([task1(), task2()]);
and
const t1 = task1();
const t2 = task2();
const result1 = await t1;
const result2 = await t2;
and
const [t1, t2] = [task1(), task2()];
const [result1, result2] = [await t1, await t2];
Is there any difference between:
const [result1, result2] = await Promise.all([task1(), task2()]);
and
const t1 = task1();
const t2 = task2();
const result1 = await t1;
const result2 = await t2;
and
const [t1, t2] = [task1(), task2()];
const [result1, result2] = [await t1, await t2];
Share
Improve this question
edited Jul 24, 2017 at 16:10
Hidden
asked Jul 24, 2017 at 15:58
HiddenHidden
4,2953 gold badges12 silver badges11 bronze badges
1
- closely related: Waiting for more than one concurrent await operation – Bergi Commented Aug 14, 2019 at 18:20
6 Answers
Reset to default 413Note: this answer just covers the timing differences between await
in series and Promise.all
. Be sure to read @mikep's comprehensive answer that also covers the more important differences in error handling.
For the purposes of this answer I will be using some example methods:
res(ms)
is a function that takes an integer of milliseconds and returns a promise that resolves after that many milliseconds.rej(ms)
is a function that takes an integer of milliseconds and returns a promise that rejects after that many milliseconds.
Calling res
starts the timer. Using Promise.all
to wait for a handful of delays will resolve after all the delays have finished, but remember they execute at the same time:
Example #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()
This means that Promise.all
will resolve with the data from the inner promises after 3 seconds.
But, Promise.all
has a "fail fast" behavior:
Example #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()
If you use async-await
instead, you will have to wait for each promise to resolve sequentially, which may not be as efficient:
Example #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()
First difference - Fail Fast
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 is only faster in the failure scenario (when some task fails). And I ask, why not? If I have two async concurrent 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 failure scenario. But OK - in this first difference you can decide which alternative to use: Promise.all
vs. multiple await
.
Second difference - Error Handling
But when considering error handling, you must use Promise.all
. It is not possible to correctly handle errors of async concurrent tasks triggered with multiple await
s. In the failure 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', e => {})
and process.on('rejectionHandled', e => {})
but this is not good practice. I have found many examples on the web that do not consider error handling for two or more async concurrent tasks at all, or consider it but in the wrong way - just using try
-catch
and hoping it will catch errors. It is almost impossible to find good practice in this.
Summary
TL;DR: Never use multiple await
for two or more async concurrent tasks, because you will not be able to handle errors correctly. Always use Promise.all
for this use case.
async
-await
is not a replacement for promises, it is just a pretty way to use promises. Async code is written in sync style and we can avoid multiple then
s in promises.
Some people say that when using Promise.all
we cannot handle task errors separately, and that we can only handle the error from the first rejected promise (separate handling can be useful, for instance for logging). This is not a problem - see the ‘Addition’ heading at the bottom of this answer.
Examples
Consider this async task.
function task(id, duration, fail) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (fail) {
reject(new Error(`Task ${id} failed!`));
} else {
resolve(`Task ${id} succeeded!`);
}
}, duration);
});
}
When you run tasks in the success scenario there is no difference between Promise.all
and multiple await
s. Both examples end with Task 1 succeeded! Task 2 succeeded!
after 5 seconds.
// Promise.all alternative
async function run() {
// run tasks concurrently
const t1 = task(1, 5000, false);
const t2 = task(2, 5000, false);
// wait for both results
const [r1, r2] = await Promise.all([t1, t2]);
console.log(r1, r2);
}
run();
// at the 5th second: Task 1 succeeded! Task 2 succeeded!
// multiple await alternative
async function run() {
// run tasks concurrently
const t1 = task(1, 5000, false);
const t2 = task(2, 5000, false);
// wait for each result sequentially
const r1 = await t1;
const r2 = await t2;
console.log(r1, r2);
}
run();
// at the 5th second: Task 1 succeeded! Task 2 succeeded!
However when you run task in the failure scenario, for instance if the first task takes 10 seconds and succeeds and the second task takes 5 seconds and fails, there are differences in the errors issued.
// Promise.all alternative
async function run() {
const t1 = task(1, 10000, false);
const t2 = task(2, 5000, true);
const [r1, r2] = await Promise.all([t1, t2]);
console.log(r1, r2);
}
run();
// at the 5th second: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// multiple await alternative
async function run() {
const t1 = task(1, 10000, false);
const t2 = task(2, 5000, true);
const r1 = await t1;
const r2 = await t2;
console.log(r1, r2);
}
run();
// at the 5th second: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at the 10th second: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
// at the 10th second: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
We should already notice here that we are doing something wrong when using multiple await
s sequentially. Let us try handling the errors:
// Promise.all alternative
async function run() {
const t1 = task(1, 10000, false);
const t2 = task(2, 5000, true);
const [r1, r2] = await Promise.all([t1, t2]);
console.log(r1, r2);
}
run().catch(e => {
console.error('Caught error', e);
});
// at the 5th second: Caught error [Error: Task 2 failed!]
As you can see, to successfully handle errors, we need to add just one catch to the run
function and add code with catch logic into the callback. We do not need to handle errors inside the run
function because async functions do this automatically - promise rejection of the task
function causes rejection of the run
function.
To avoid a callback we can use sync style (async
-await
and try
-catch
) try { await run(); } catch (e) { console.error('Caught error', e); }
, but in this example it is not possible because we cannot use await
in the main thread - it can only be used in async functions (because nobody wants to block main thread). To test if handling works in sync style we can call the run
function from another async function or use an IIFE (Immediately Invoked Function Expression: MDN):
// Promise.all alternative
async function run() {
const t1 = task(1, 10000, false);
const t2 = task(2, 5000, true);
const [r1, r2] = await Promise.all([t1, t2]);
console.log(r1, r2);
}
(async function () {
try {
await run();
} catch (e) {
console.error('Caught error', e);
}
})();
This is the only correct way to run two or more async concurrent tasks and handle errors. You should avoid the examples below.
Bad Examples
// multiple await alternative
async function run() {
const t1 = task(1, 10000, false);
const t2 = task(2, 5000, true);
const r1 = await t1;
const r2 = await t2;
console.log(r1, r2);
}
run().catch(e => {
console.error('Caught error', e);
});
// at the 5th second: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at the 10th second: Caught error [Error: Task 2 failed!]
// at the 10th second: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
We see that the error for task 2 was not handled and later that it was caught. Misleading and still full of errors in console, it is still unusable this way. We 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(() => {
throw new Error();
}, 0);
}
try {
test();
} catch (e) { // this will never catch the error
console.error('Caught error', e);
}
Another poor example:
async function run() {
try {
const t1 = task(1, 10000, false);
const t2 = task(2, 5000, true);
const r1 = await t1;
const r2 = await t2;
console.log(r1, r2);
} catch (e) {
console.error('Caught inner error', e);
}
}
run().catch(e => {
console.error('Caught error', e);
});
// at the 5th second: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at the 10th second: Caught inner error [Error: Task 2 failed!]
// at the 10th second: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
Only two errors (3rd one is missing) but nothing is caught.
Addition - Handling Separate Task Errors
async function run() {
const t1 = task(1, 10000, true).catch(e => {
console.error('Task 1 failed!');
throw e;
});
const t2 = task(2, 5000, true).catch(e => {
console.error('Task 2 failed!');
throw e;
};
const [r1, r2] = await Promise.all([t1, t2]);
console.log(r1, r2);
}
run().catch(e => {
console.error('Run failed (does not matter which task)!');
});
// at the 5th second: Task 2 failed!
// at the 5th second: Run failed (does not matter which task)!
// at the 10th second: Task 1 failed!
Note that in this example I rejected both tasks to better demonstrate what happens (throw e
is used to fire the fail-fast error).
Generally, using Promise.all()
runs requests "async" in parallel. Using await
can run in parallel OR be "sync" blocking.
test1 and test2 functions below show how await
can run async or sync.
test3 shows Promise.all()
that is async.
jsfiddle with timed results - open browser console to see test results
Sync behavior. Does NOT run in parallel, takes ~1800ms:
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
};
Async behavior. Runs in paralel, takes ~600ms:
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
}
Async behavior. Runs in parallel, takes ~600ms:
const test3 = async () => {
await Promise.all([
Promise.delay(600),
Promise.delay(600),
Promise.delay(600)]); //runs all delays simultaneously
};
TLDR; If you are using Promise.all()
it will also "fast-fail" - stop running at the time of the first failure of any of the included functions.
You can check for yourself.
In this fiddle, I ran a test to demonstrate the blocking nature of await
, as opposed to Promise.all
which will start all of the promises and while one is waiting it will go on with the others.
In case of await Promise.all([task1(), task2()]); "task1()" and "task2()" will run parallel and will wait until both promises are completed (either resolved or rejected). Whereas in case of
const result1 = await t1;
const result2 = await t2;
t2 will only run after t1 has finished execution (has been resolved or rejected). Both t1 and t2 will not run parallel.
Just in case, in addition to the already awesome answers:
const rejectAt = 3;
// No worries. "3" is purely awesome, too! Just for the tiny example!
document.body.innerHTML = '';
o("// With 'Promise.all()':");
let a = Promise.all([
test(1),
test(2),
test(3),
test(4),
test(5),
]).then(v => {
o(`+ Look! We got all: ${v}`);
}).catch(e => {
o(`x Oh! Got rejected with '${e}'`);
}).finally(() => {
o("\n// With 'await':");
async function test2() {
try {
r = [];
r.push(await test(1));
r.push(await test(2));
r.push(await test(3));
r.push(await test(4));
r.push(await test(5));
o(`+ Look! We got all: ${r.join(',')} // Twice as happy! ^^`);
} catch (e) {
o(`x Ah! Got rejected with '${e}'`);
}
}
test2();
});
function test(v) {
if (v === rejectAt) {
o(`- Test ${v} (reject)`);
return new Promise((undefined, reject) => reject(v));
}
o(`- Test ${v} (resolve)`);
return new Promise((resolve, undefined) => resolve(v));
}
// ----------------------------------------
// Output
function o(value) {
document.write(`${value}\n`);
}
body {
white-space: pre;
font-family: 'monospace';
}
A possible result:
// With 'Promise.all()':
- Test 1 (resolve)
- Test 2 (resolve)
- Test 3 (reject)
- Test 4 (resolve)
- Test 5 (resolve)
x Oh! Got rejected with '3'
// With 'await':
- Test 1 (resolve)
- Test 2 (resolve)
- Test 3 (reject)
x Ah! Got rejected with '3'
本文标签: javascriptAny difference between await Promiseall() and multiple awaitStack Overflow
版权声明:本文标题:javascript - Any difference between await Promise.all() and multiple await? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736714292a1949115.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论