tnsaturday
tnsaturday

Reputation: 603

Unexpected Promise.all() fail fast behavior - continues to execute after rejection

From MDN:

Promise.all is rejected if any of the elements are rejected. For example, if you pass in four promises that resolve after a timeout and one promise that rejects immediately, then Promise.all will reject immediately.

Let's discuss the following code snippet:

(async () => {
    try {
    const awaited = await Promise.all([
        (() => {
            console.log('1');
            return Promise.reject('2');
        })(),
        (() => {
            console.log('3');
            return Promise.resolve('4');
        })(),
    ]);

    console.log(awaited);
    } catch (error) {
    console.log(error);
    } finally {
    console.log('5');
    }
})();

All the promises in the code above are immediately resolved/rejected. I think that the code should execute this way:

  1. First console.log is executed, thus logging '1';
  2. Promise is rejected with the value '2' thus fast-failing Promise.all();
  3. 'Catch' clause is executed, logging the rejected value of '2';
  4. 'Finally' clause is executed regardless, logging '5';

Instead, I see a '3' also being logged. Why is it so?

Upvotes: 0

Views: 1154

Answers (3)

Felix Kling
Felix Kling

Reputation: 817000

Instead, I see a '3' also being logged. Why is it so?

All the other answers try to explain how promises or the event loop work, but in fact it has nothing to do with those.

The argument you pass to Promise.all is an array that contains the results of two function calls. Therefore both functions will be called before Promise.all is called. Both functions console.log a value and then return a promise. Promise.all receives an array containing those promises. So before Promise.all does anything, both functions have already been executed and logged 1 and 3 respectively.

Maybe it's easier to see you restructure your code to create the promises beforehand:

(async () => {
    try {
    const promise1 = (() => {
        console.log('1');
        return Promise.reject('2');
    })();
    const promise2 = (() => {
        console.log('3');
        return Promise.resolve('4');
    })()

    const awaited = await Promise.all([promise1, promise2]);

    console.log(awaited);
    } catch (error) {
    console.log(error);
    } finally {
    console.log('5');
    }
})();

Just like with any other function, all function arguments are evaluated before the function itself is called. The order of events is rather this:

  1. First console.log is executed, thus logging '1';
  2. Rejected promise with 2 is created.
  3. Second console.log is executed, thus logging '3';
  4. Fulfilled promise with 4 is created.
  5. Array containing both promises is created.
  6. Promise.all is called with array of promises as parameter.
  7. Promise.all processes promises.
  8. 'Catch' clause is executed, logging the rejected value of '2';
  9. 'Finally' clause is executed regardless, logging '5';

Here is a simpler example: Note that nothing is "done" with the second argument passed to the function and yet the value is still logged, precisely because all arguments are evaluated first:

function process(arg) {
  return 'Received ' + arg;
}


const result = process(
  (() => {
    console.log('1');
    return 1;
  })(),
  (() => {
    console.log('3');
    return 2;
  })(),
);
console.log(result)

Upvotes: 2

samanime
samanime

Reputation: 26615

The Promise.all() does fast-fail. However, it doesn't abort the other Promises from finishes. This example with timeouts illustrates it a little better.

Promise.all([
    Promise.reject(),
    new Promise((resolve, reject) => setTimeout(() => { console.log('a'); resolve(); }, 500)),
    new Promise((resolve, reject) => setTimeout(() => { console.log('b'); resolve(); }, 2000))
])
  .then(() => console.log('done'))
  .catch(() => console.log('fail'));

You can see that the Promise.all() calls the catch() almost immediately, and stops waiting for the results of the other, because it won't change the outcome. However, the other two Promises aren't aborted in any way.

If you want to abort them, you'd have to implement your own logic to do that.

Upvotes: 0

AGoranov
AGoranov

Reputation: 2244

This is due to the way that Promises are handled in JavaScript. It is asynchronous in nature and there is no security as to the execution order. Please note that this is not an error but rather a feature of the language.

I would suggest you read more on the issue in this SO question and about the event loop.

Upvotes: -1

Related Questions