15

In my code, I am using Promise.all() to run code asynchronously once some promises have all fulfilled. Sometimes, one promise will fail, and I'm not sure why. I would like to know which promise is failing. Passing a callback as a second parameter to the .then method does not help much, as I know that a promise is rejecting but not which promise is rejecting.

A stack trace does not help either, as the first item is the Promise.all()'s error handler. A line number from the Error object passed to the first parameter of the second function passed to the try function of the Promise.all() is simply the line number of the line where I log the line number.

Does anybody know of a way to find out which promise is rejecting?

3 Answers3

19

You can use an onreject handler on each promise:

Promise.all(promises.map((promise, i) =>
    promise.catch(err => {
        err.index = i;
        throw err;
    });
)).then(results => {
    console.log("everything worked fine, I got ", results);
}, err => {
    console.error("promise No "+err.index+" failed with ", err);
});

In general, every rejection reason should contain enough information to identify the issue that you need to handle (or log).

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Why use an `onreject` handler instead of `.catch()`? Isn't this the [`.then(success, fail)`](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns#the-thensuccess-fail-anti-pattern) antipattern? – JLRishe Feb 02 '17 at 17:56
  • 1
    I believe the above example is just for testing-purposes so you can see which one rejected. – Harry Pehkonen Feb 02 '17 at 18:10
  • @JLRishe [It's not an antipattern](http://stackoverflow.com/q/24662289/1048572). I want to see exactly one of the logs, never both. If I made a mistake in the callbacks, I want to get an unhandled rejection. – Bergi Feb 02 '17 at 18:30
  • Is `promises` assumed to be an array of promises? –  Feb 02 '17 at 18:48
  • @programmer5000 Yes. If you don't have an arbitrary array but a constant number of promises, I'd suggest using distinct error handlers that add specific information instead of my generic one that only knew about indices. – Bergi Feb 02 '17 at 18:54
  • I see you are only passing array of `promise.catch`-es to the `Promise.all` - shouldn't you pass array of `promises` to `Promise.all` too? Can you explain this a bit? – Giorgi Moniava Nov 18 '18 at 16:00
  • @giorgim `promise.catch(…)` returns (like `promise.then(…)`) a new promise. *Those* are passed to `Promise.all`. – Bergi Nov 18 '18 at 17:18
  • @Bergi Yeah I noticed that but is that promise same as the original promise on which we applied catch? What confuses me apparently is that I was expecting to see the original promise (the one in which we are interested to be resolved) passed to resolve.all. – Giorgi Moniava Nov 18 '18 at 17:44
  • 1
    @giorgim No, `catch()` returns a *new* promise not the original promise. The new promise either fulfills with the same result as the original one, or when the original promise rejects then it resolves to the result of the callback. – Bergi Nov 18 '18 at 17:48
  • 1
    @Bergi "The new promise either fulfills with the same result as the original one"-> Ah, I think this is the piece I was missing, if that's the case, now it all makes sense right? – Giorgi Moniava Nov 18 '18 at 18:02
2

this wrapper will wait for and return every result and/or rejection

the returned array will be objects

{ // this one resolved
    ok: true,
    value: 'original resolved value'
},
{ // this one rejected
    ok: false,
    error: 'original rejected value'
},
{ // this one resolved
    ok: true,
    value: 'original resolved value'
},
// etc etc

One caveat: this will wait for ALL promises to resolve or reject, not reject as soon as the first rejection occurs

let allresults = function(arr) {
    return Promise.all(arr.map(item => (typeof item.then == 'function' ? item.then : Promsie.resolve(item))(value => ({value, ok:true}), error => ({error, ok:false}))));
}

allresults(arrayOfPromises)
.then(results => {
    results.forEach(result => {
        if(result.ok) {
            //good
            doThingsWith(result.value);
        } else {
            // bad
            reportError(result.error);
        }
    });
});
Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
  • Your "caveat" is actually something helpful that I will use in other projects, although something like the first answer would be more helpful for this one. Thanks anyways! –  Feb 03 '17 at 13:40
  • There's `Promise.allSettled` that does exactly this. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled – Jen Jun 28 '23 at 10:35
  • @Jen not in 2017 :p – Jaromanda X Jun 28 '23 at 10:56
  • yeah, I only checked the date of your answer after writing my comment. I should have written "nowadays". – Jen Jun 28 '23 at 11:00
  • all good @Jen - as it's not the accepted answer, I won't bother editing it – Jaromanda X Jun 28 '23 at 11:02
0

Since about 2019 we have the Promise.allSettled which (always) resolves once all the given promises resolve or reject. The resolved value then is an array of objects that indicate the result of each given promise.

Eg.

const results = await Promise.allSettled(promises_array);
results.forEach((result, index) => {
  if (result.status === "rejected") {
    console.error(`promise #${index} rejected with ${result.reason}`);
  }
  // Could be written with simple `else`. `status` can only have these two values.
  if (result.status === "fulfilled") {
    console.log(`promise #${index} resolved to ${result.value}`);
  }
});
Jen
  • 1,206
  • 9
  • 30