Race Conditions and Fallacies of `Promise.all`
Tags: javascript, promise, programming, async
Published: 2022-02-18 15:14:14

---
title: Race Conditions and Fallacies of `Promise.all`
published: true
description: 
tags: javascript,promise,programming,async
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8b0jkmrx6sfw3vil9clj.png
---

## What fallacies does `Promise.all` have?

[Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) is a convinient synchronization mechanism. However, unbeknownst to many, when facing rejections, `Promise.all` can cause two big headaches:


1. Upon the first rejection, the promise created with `Promise.all` will settle with that first rejection. All its other unsettled promises are now "dangling". This means that code chained/nested in those promises is now running concurrently with all code that is chained after `Promise.all`. This can lead to ugly race conditions if you are not aware of and explicitely consider that possibility.
2. If you have more than one rejection, any rejection that is not the first, not only is "dangling", but is explicitly muted. The JS engine will not report any of its unhandled rejections, that is not the first.


## What about `Promise.allSettled`?

[Promise.allSettled](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) is not quite as user-friendly and even foregoes promise error/rejection handlers. You have to provide a fulfillment handler and manually loop over the `results` array to decipher whether you have any errors at all (i.e. `Promise.allSettled(...).then(results => ...)`). It's OK, if you make sure that you diligently handle **all** the information, but it makes things quite a bit more convoluted.


## Solution

I present an alternative to `Promise.all` which uses `Promise.allSettled` and aggregates all errors.

NOTE: Just like, `Promise.all`, it ignores fulfilled values, in case of any rejection.

### Pros:
* Easy to use alternative to `Promise.all` that does not allow dangling, thereby preventing race conditions.
* Reports all errors, not only the first

### Cons:
* Error aggregation mangles error objects into one big string. That can be further improved.
* Promise chain not moving forward on first rejection can slow things down considerably in case the first error happens fast, but the critical path is slow.


The following code is also available in [this gist](https://gist.github.com/Domiii/41c1dc504025e789fd8741f78d3ac528):

```js
/**
 * Fixes the "dangling problem" of Promise.all.
 *
 * {@link betterPromiseAll}
 * @see https://dev.to/domiii/a-solution-to-the-deep-flaws-of-promiseall-4aon-temp-slug-8454028
 */
async function promiseAll(promises) {
  const results = await Promise.allSettled(promises);
  const values = [];
  const errors = [];

  for (const result of results) {
    if (result.status === 'rejected') {
      errors.push(result.reason);
    }
    else {
      values.push(result.value);
    }
  }

  if (errors.length) {
    // NOTE: `AggregateError` seems not too mature YET. It's internal `errors` property is (currently, as of 2/2022) NOT rendered when reported, so we do some manual aggregation for now.
    // throw new AggregateError(errors, 'Promise.allSettled rejections');
    throw new Error(`${errors.length} promise rejections: ${errors.map((err, i) => `\n  [${i + 1}] ${err.stack || err}`).join('')}\n------`);
  }
  return values;
}

/** ###########################################################################
 * some samples
 * ##########################################################################*/
async function runSample(cb) {
  try {
    const result = await cb();
    console.log('########\nFULFILL:\n', result);
  }
  catch (err) {
    console.error('########\nREJECT:\n', err);
  }
}

// reject
runSample(() => {
  return betterPromiseAll([
    Promise.reject(1), 
    Promise.reject(new Error(2)), 
    Promise.resolve().then(() => { throw new Error(3); })
  ]);
});

// reject
runSample(() => {
  return betterPromiseAll([
    Promise.resolve(1),
    Promise.reject(new Error(2)), 
    Promise.resolve().then(() => { throw new Error(3); })
  ]);
});

// fulfill
runSample(() => {
  return betterPromiseAll([
    Promise.resolve(1),
    Promise.resolve(2),
    Promise.resolve(3)
  ]);
});
```