7 min read

Understanding `Promise.all()`, `Promise.race()`, and `Promise.any()`

Table of Contents

Cover Image

JavaScript gives us several ways to coordinate multiple asynchronous operations. Three of the most useful are Promise.all(), Promise.race(), and Promise.any().

They all accept a list of Promises, but they answer different questions:

  • Promise.all(): Did every task succeed?
  • Promise.race(): Which task finished first?
  • Promise.any(): Which task succeeded first?

The Core Difference

Promise.all() fulfills only when every Promise fulfills. If one Promise rejects, the whole group rejects.

Promise.race() settles as soon as the first Promise settles. If the fastest Promise fulfills, the race fulfills. If the fastest Promise rejects, the race rejects.

Promise.any() fulfills as soon as the first Promise fulfills. It ignores rejections unless every Promise rejects. If all Promises reject, it throws an AggregateError.

A Shared Async Helper

To make the difference visible, imagine an upload or load operation that takes a random amount of time and has a 50% chance of success.

The demo code uses this helper:

const upload = function (blob) {
  let time = Math.round(100 + 500 * Math.random());

  return new Promise((resolve, reject) => {
    console.log(`run ${time}ms`);

    if (Math.random() > 0.5) {
      setTimeout(resolve, time, 'promise resolved ' + time + 'ms');
    } else {
      setTimeout(reject, time, 'promise rejected ' + time + 'ms');
    }
  });
};

const load = function (url) {
  return upload(url);
};

Each call produces a Promise that either resolves or rejects after a randomized delay. This makes it perfect for showing how each Promise combinator behaves.

Demo animation

🎮 Try it live: Open the interactive demo to experience this yourself.

Promise.all(): Wait for Everything to Succeed

Use Promise.all() when every operation is required.

For example, if you are uploading three images and the page should continue only after all three have uploaded, Promise.all() is the right tool.

(async () => {
  try {
    let result = await Promise.all([
      upload(0),
      upload(1),
      upload(2)
    ]);

    console.log(result);
  } catch (err) {
    console.error(err);
  }
})();

If all three uploads resolve, result is an array containing each resolved value.

If any one upload rejects, Promise.all() rejects immediately with that error.

With three independent tasks and a 50% success rate per task, the chance that all three succeed is:

0.5 * 0.5 * 0.5 // 0.125, or 12.5%

Demo animation

🎮 Try it live: Open the interactive demo to experience this yourself.

Promise.race(): Use the First Finished Result

Promise.race() is about speed, not success.

It settles with whichever Promise finishes first, whether that Promise resolves or rejects.

(async () => {
  try {
    let result = await Promise.race([
      load(0),
      load(1),
      load(2)
    ]);

    console.log(result);
  } catch (err) {
    console.error(err);
  }
})();

If the fastest task resolves, the race resolves.

If the fastest task rejects, the race rejects.

The important detail is that slower Promises do not affect the final result of the race. The first settled Promise decides everything.

Promise.any(): Use the First Successful Result

Promise.any() is similar to race(), but it cares only about fulfillment.

That makes it useful when you have multiple possible sources for the same result and you only need one of them to work.

(async () => {
  try {
    let result = await Promise.any([
      load(0),
      load(1),
      load(2)
    ]);

    console.log(result);
  } catch (err) {
    console.error(err);
  }
})();

If one Promise fulfills, Promise.any() fulfills with that value.

If one Promise rejects, Promise.any() keeps waiting for another Promise to fulfill.

Only when every Promise rejects does it reject with an AggregateError.

AggregateError: All promises were rejected

With three independent tasks and a 50% failure rate per task, the chance that all three fail is 12.5%. So the chance that at least one succeeds is:

1 - 0.5 * 0.5 * 0.5 // 0.875, or 87.5%

Demo animation

🎮 Try it live: Open the interactive demo to experience this yourself.

A Practical Promise.all() Use Case: Minimum Loading Time

Sometimes a request finishes so quickly that a loading spinner flashes for a split second. That can feel worse than showing nothing.

One solution is to wait for both the request and a minimum timer:

let timeout = function (delay, result) {
  return new Promise(resolve => {
    setTimeout(() => resolve(result), delay);
  });
};

showToast();

Promise.all([
  showUserInfo(),
  timeout(200)
]).then(() => {
  hideToast();
});

This guarantees that the loading indicator stays visible for at least 200 ms.

It is a good fit for Promise.all() because both things must complete before hiding the loading UI:

  • the real request
  • the minimum display duration

A More Refined Loading Strategy with Promise.race()

But there is a better version of this interaction.

If the request finishes within 200 ms, maybe the loading indicator should never appear. If the request takes longer, then show loading and keep it visible long enough to feel intentional.

That is where Promise.race() helps:

let promiseUserInfo = showUserInfo();

Promise.race([
  promiseUserInfo,
  timeout(200)
]).then((display) => {
  if (!display) {
    showToast();

    Promise.all([
      promiseUserInfo,
      timeout(200)
    ]).then(() => {
      hideToast();
    });
  }
});

Here, the request races against a 200 ms timer.

If the request wins, no spinner appears.

If the timer wins, the spinner appears, and then Promise.all() ensures it stays visible for at least 200 ms.

A Practical Promise.any() Use Case: Fastest Available CDN

Promise.any() is ideal when you can request the same resource from multiple places.

For example, you might try loading Vue from both unpkg and jsDelivr, then use whichever succeeds first:

let startTime = +new Date();

let importUnpkg = loadResource('unpkg', 715, true);
let importJsdelivr = loadResource('jsDelivr', 83, true);

Promise.any([
  importUnpkg,
  importJsdelivr
]).then(resource => {
  console.log(
    'Loading complete. Time: ' +
    (+new Date() - startTime) +
    'ms'
  );

  console.log(resource.version);
});

If jsDelivr is faster, you use jsDelivr.

If unpkg is faster, you use unpkg.

If one CDN fails, the other can still succeed. That is the main advantage of Promise.any() over Promise.race() in this scenario.

Compatibility Note

Promise.all() and Promise.race() have been available for a long time.

Promise.any() arrived later, so if you support older browsers, check for support or include a fallback:

const supportsPromiseAny = typeof Promise.any === 'function';

if (supportsPromiseAny) {
  console.log('Promise.any() is supported in this browser.');
} else {
  console.log('Promise.any() needs a polyfill or fallback.');
}

Quick Decision Guide

Use Promise.all() when every task must succeed.

Use Promise.race() when the first completed task should decide the result.

Use Promise.any() when the first successful task should decide the result.

The subtle but important distinction is this:

race() cares about who finishes first.

any() cares about who succeeds first.

That single difference changes the kind of problems each method solves.


Try It Yourself

Want to see these concepts in action? I’ve created an interactive demo where you can experiment with the code and see real-time results.

View the Live Demo

Explore more demos from my previous articles in the Demo Gallery.

Happy coding!