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
Add a comment  | 

6 Answers 6

Reset to default 413

Note: 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 awaits. 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 thens 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 awaits. 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 awaits 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