Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why does Promise.any() resolve with the returned value of the catch handler of a rejected promise instead of waiting for the first fulfilled one?

Please consider the following code:

const race = () => Promise.any([
    // first promise, rejects in 100
    new Promise((resolve, reject) => {
      setTimeout(reject, 100, 1);
    })
    .then(r => (console.log('one resolved with', r), r))
    .catch(err => (console.warn('one rejected with', err), err)),

    // second promise, resolves in 200
    new Promise((resolve, reject) => {
      setTimeout(resolve, 200, 2);
    })
    .then(r => (console.log('two resolved with', r), r))
    .catch(err => (console.warn('two rejected with', err), err))
  ])
  .then(r => console.log('race resolved with', r))
  .catch(err => console.warn('race rejected with', err))

race()

Note For brevity, I used comma expressions. If you prefer a more traditional syntax, here’s the long form:

const race = () => Promise.any([
    new Promise((resolve, reject) => {
      setTimeout(reject, 100, 1);
    })
    .then(r => {
      console.log('one resolved with', r);
      return r;
    })
    .catch(err => {
      console.warn('one rejected with', err);
      return err;
    }),
    new Promise((resolve, reject) => {
      setTimeout(resolve, 200, 2);
    })
    .then(r => {
      console.log('two resolved with', r);
      return r;
    })
    .catch(err => {
      console.warn('two rejected with', err);
      return err;
    })
  ])
  .then(r => console.log('race resolved with', r))
  .catch(err => console.warn('race rejected with', err))

race()

I’m expecting race() to resolve with 2, in 200. Instead, it resolves with the return value of the first promise’s catch handler (1), in 100, (even if it’s not explicit – e.g: If my catch wouldn’t actually return the error. The .any() wrapper would still resolve with undefined, instead of waiting for the first fulfilled promise. Resolve, not even reject!).

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

What’s even funnier is that the expected behavior does happen if I do not specify a catch handler in the failing promise. Have a look:

const race = () => Promise.any([

    new Promise((resolve, reject) => {
      setTimeout(reject, 100, 1);
    })
    .then(r => (console.log('one resolved with', r), r)),

    new Promise((resolve, reject) => {
      setTimeout(resolve, 200, 2);
    })
    .then(r => (console.log('two resolved with', r), r))

  ])
  .then(r => console.log('race resolved with', r))
  .catch(err => console.warn('race rejected with', err))

race()

Maybe I want to log failures! Why does having some code run on rejection make the parent Promise.any() resolve with return value of the first failed promise which happens to have a .catch handler, instead of waiting for the first fulfilled promise, as advertised?

Shouldn’t the parent Promise.any() have the same behavior regardless of whether the catch callback is defined or not?

Is this a bug? I find it mind boggling.

>Solution :

When you chain a .catch onto a Promise, the whole expression will evaluate to a resolved Promise, that evaluates to the value returned at the end of the .catch. See how the below does not result in the second .catch being entered into.

Promise.reject()
  .catch((err) => { console.log('first catch'); })
  .catch((err) => { console.log('second catch'); })

// In other words, the whole below expression resolves to a resolved Promise:
//   Promise.reject()
//     .catch((err) => { console.log('first catch'); })

For the whole

somePromise
  .catch(hander)

to result in a rejected Promise, the handler would have to throw (or construct and return a rejected Promise).

So, in your original code:

.catch(err => (console.warn('one rejected with', err), err)),

This makes that whole expression resolve. If you want it to reject, change it to:

.catch(err => {
  console.warn('one rejected with', err);
  throw err;
}),

(throw needs to be a standalone statement, so it can’t be used with the comma operator)

const race = () => Promise.any([
    // first promise, rejects in 100
    new Promise((resolve, reject) => {
      setTimeout(reject, 100, 1);
    })
    .then(r => (console.log('one resolved with', r), r))
    .catch(err => {
      console.warn('one rejected with', err);
      throw err;
    }),

    // second promise, resolves in 200
    new Promise((resolve, reject) => {
      setTimeout(resolve, 200, 2);
    })
    .then(r => (console.log('two resolved with', r), r))
    .catch(err => (console.warn('two rejected with', err), err))
  ])
  .then(r => console.log('race resolved with', r))
  .catch(err => console.warn('race rejected with', err))

race()

A common approach to a similar issue is to just not .catch unless you can do something useful with the error – and if not, just let the rejected Promise percolate back up to its caller, so that its caller can take care of it. While you can .catch and re-throw, it’s a tiny bit ugly, so you probably don’t want to do it unless necessary.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading