5

Promise.all([iterable]) is all or nothing, meaning that the promise it returns resolves when every promise in the iterable resolves, or rejects as soon as one of the promises rejects, with the reason of the first promise that rejects (doc).

But what happens if multiple promises of the iterable reject?

In VSCode, I tried the following example, and purposely made both foo() and bar() promises fail. When I debug in VSCode I get an error on * catch(err => Promise.reject('error query bar()'))* saying Exception has occurred and I don't understand why.

I think it is because when I call Promise.reject the Promise.all has already received a reject from the foo function that also fails, but it is not clear what is happening.

If I disable the "Uncaught Exceptions" breakpoint in the debugging options, the exception doesn't show up anymore.

What exactly is happening here?

function foo() {
  return pool.query('insert_test into test (value) values (20)')
    .then(() => client.query('insert into test (value) values (21)'))
    .catch(err => Promise.reject('error query bar()'))
}

function bar() {
  return pool.query('insert_test into test (value) values (20)')
    .then(() => client.query('insert into test (value) values (21)'))
    .catch(err => Promise.reject('error query bar()'))
 }

 Promise.all([foo(), bar()])
   .then(results => {
     console.log(results)
   })
   .catch(err => {
     console.log(err)
   });

This is a screenshot of what I see with I have Uncaught Exceptions enabled. enter image description here

smellyarmpits
  • 1,080
  • 3
  • 13
  • 32
  • 1
    You don't need to call `Promise.reject()` in catch, since catch is a Promise function (See here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch). Further, you do not have to catch errors in `foo()` nor in `bar()`, the catch-Function in `Promise.all` handles all rejections! – noChance Dec 01 '17 at 12:04
  • @noChance I think he did that to distinguish errors from `foo` from errors from `bar` – Bergi Dec 01 '17 at 12:35
  • @noChance thank you for your answer. Yeah, I wanted to distinguish bar's error from foo's error. But more importantly, if I don't call Promise.reject, it won't show up in the Promise.all catch, but in the then, right? – smellyarmpits Dec 01 '17 at 13:49
  • I am considering the case where I would substitute .catch(err=>Promise.reject()) with .catch(err=>{}) – smellyarmpits Dec 01 '17 at 13:55
  • `.catch(err=>{})` resolves the promise. sometimes not what we want. – André Werlang Dec 01 '17 at 14:06
  • 1
    @MarcoGalassi No, the the catch function of the Promise.all handles all errors. As you stated: _Promise.all([iterable]) is all or nothing_ . So you can queue as many promises as you want in foo() or bar(), but any error will be handled by the outer Promise.all.catch-function. – noChance Dec 01 '17 at 14:14
  • @AndréWerlang yes, I mean that, exactly, catch(err=> {}) resolves the promise and so the flow will end up in the then of the caller – smellyarmpits Dec 01 '17 at 14:18
  • @noChance yeah yeah, but if I want to reject with a particular error, or I need to process the error somewhat before rejecting, I need to handle that in the catch and than explicitly return Promise.reject. Right? – smellyarmpits Dec 01 '17 at 14:20
  • @MarcoGalassi If you want to know, which function threw the error, you could implement something similiar to [this](https://stackoverflow.com/a/42009012/8029464). – noChance Dec 01 '17 at 14:35
  • @noChance that will hurt maintenance. Imagine you add another promise among the others, you'll have to change the global rejection because now you rely on indexes. – André Werlang Dec 01 '17 at 14:57
  • in general, attaching a catch to each individual promise is better, also when chaining, you want to catch in the exact spot because you'll deal with the proper rejections. – André Werlang Dec 01 '17 at 14:59
  • @AndréWerlang ok, this is my approach too. I understand that yes you could avoid explicitly rejecting a promise and let the rejection be handled in the next catch, but I don't find it particularly helpful if the reason is just to save a line of code – smellyarmpits Dec 01 '17 at 15:06
  • From your code, you are not really using [pg-promise](https://github.com/vitaly-t/pg-promise), are you? If so, then please remove `pg-promise` label, as it is not applicable. And if you are, then clarify, as the answer would be quite different. – vitaly-t Dec 01 '17 at 23:38
  • @vitaly-t you are correct, my bad. It was a confusion using both pg and promises – smellyarmpits Dec 04 '17 at 07:58

2 Answers2

4

But what happens if multiple promises of the iterable reject?

The first rejection wins and gets its reason to reject the Promise.all promise. "First" in here means "happening earliest", if the promises already were rejected when Promise.all visits them iteration order is important.

The subsequent rejections will be ignored.

If I disable the "Uncaught Exceptions" breakpoint in the debugging options, the exception doesn't show up anymore.

That's weird. An ignored rejection should not cause an unhandled rejection (or uncaught exception).

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
-1

@smellyarmpits If you want to prevent uncaught exception for remaining promises that gets rejected after the first promise is rejected and therefore after the Promise.all is rejected, to can try a custom implementation of Promise.all that wraps the original one like this (typescript) :

  const promiseAll = async (promises: Promise<any>[]) => {
    let oneHasRejected = false
    const onReject = (err: any) => {
      if (oneHasRejected) {
        oneHasRejected = true
        return Promise.reject(err)
      } else {
        return Promise.resolve()
      }
    }
    return Promise.all(promises.map((p) => p.then((r) => r, onReject)))
  }