Promise.all() vs. Promise.race() in JavaScript

Hero image for Promise.all() vs. Promise.race() in JavaScript. Image by Paul Pastourmatzis.
Hero image for 'Promise.all() vs. Promise.race() in JavaScript.' Image by Paul Pastourmatzis.

Once developers move past the first "what is a promise?" stage, the next source of confusion is usually not promise syntax. It is promise coordination.

One async task is easy enough to picture. Three async tasks at once is where the behaviour starts mattering:

  • should we wait for all of them
  • should we continue as soon as one settles
  • what happens if one fails

That is where Promise.all() and Promise.race() come in.

They are often mentioned together because both accept multiple promises, but they solve very different problems.


Promise.all() waits for everything to succeed

At its simplest:

const results = await Promise.all([  fetchUser(),  fetchOrders(),  fetchRecommendations(),]);

Promise.all() waits until every promise in the array has fulfilled.

When that happens, it resolves with an array of results in the same order as the input promises.

That order point matters. Even if one request finishes first in time, the results still come back in the array positions you provided originally.


If one promise rejects, the whole Promise.all() rejects

This is the behaviour that most often surprises beginners.

If any one of the promises rejects, Promise.all() rejects immediately with that error.

So if we do this:

await Promise.all([  fetchUser(),  fetchOrders(),  fetchRecommendations(),]);

and fetchOrders() fails, the combined promise rejects even if the others succeeded.

That makes sense when the results are all required together. It is less helpful when partial success would still be useful.


Promise.all() is ideal when the tasks belong together

Good use cases include:

  • loading several pieces of page data in parallel
  • fetching related resources before rendering
  • performing independent async work where every result is required

For example:

const loadDashboard = async (): Promise<void> => {  try {    const [user, orders, recommendations] = await Promise.all([      fetchUser(),      fetchOrders(),      fetchRecommendations(),    ]);    console.log(user, orders, recommendations);  } catch (error) {    console.error('Dashboard data failed to load', error);  }};

This is much faster than awaiting each request one after another if they do not depend on each other.


Promise.race() is not about collecting everything

Promise.race() does something quite different.

It settles as soon as the first promise in the group settles, whether that first outcome is a fulfilment or a rejection.

const result = await Promise.race([  fetchPrimaryData(),  fetchFallbackData(),]);

If fetchFallbackData() resolves first, that becomes the result. If fetchPrimaryData() rejects first, the race rejects immediately.

This is not "whichever succeeds first". It is "whichever settles first".


That Distinction Matters a Lot

People often talk about Promise.race() as if it picks the first successful promise. It does not.

If the fastest promise fails, the race fails.

That makes it useful for timeout patterns, but also easy to misuse if you expected it to quietly ignore early rejections.


A Timeout Example is the Classic Use Case

Suppose we want to give a request five seconds before treating it as timed out:

const timeout = (ms: number): Promise<never> =>  new Promise((_, reject) => {    window.setTimeout(() => {      reject(new Error('Request timed out'));    }, ms);  });const loadProducts = async (): Promise<Response> => {  return Promise.race([    fetch('/api/products'),    timeout(5000),  ]);};

If fetch() finishes first, we get its response. If the timeout promise rejects first, the race rejects.

That is a practical pattern because the fastest settlement is exactly what we care about.


Promise.race() does not cancel the losers

This is another important caveat.

If one promise wins the race, the others do not magically stop existing.

That means a timedout fetch may still continue in the background unless we explicitly abort it using something like AbortController.

So Promise.race() is about choosing the first settled result, not cancelling the rest of the work.


A common performance mistake with Promise.all()

Another beginner trap is this pattern:

const user = await fetchUser();const orders = await fetchOrders();const recommendations = await fetchRecommendations();

That runs the operations one after another.

If they are independent, Promise.all() is often better:

const [user, orders, recommendations] = await Promise.all([  fetchUser(),  fetchOrders(),  fetchRecommendations(),]);

The improvement is not that promises become "more async". It is that the work is started in parallel rather than serially.


Choose the Method Based on the Question You are Asking

This is the simplest decision rule.

Use Promise.all() when the question is:

"When all of these tasks have completed successfully, give me all the results."

Use Promise.race() when the question is:

"Whichever settles first, I want that outcome."

Once you phrase the problem properly, the choice is usually obvious.


Error Handling Follows the Method's Purpose

With Promise.all(), one rejection means the group has failed.

With Promise.race(), the first rejection wins just as surely as the first fulfilment would.

That means the right error strategy depends on whether failure in one member should collapse the whole operation or whether a timeoutstyle firstsettled rule is the real goal.


These Methods are Powerful Because They Describe Intent

Good async code is not only about syntax like then() or await. It is about expressing the relationship between tasks clearly.

Promise.all() says:

  • these tasks belong together
  • I need all the results

Promise.race() says:

  • the earliest settled outcome decides what happens next

That is a meaningful difference in program behaviour, not just a minor API variation.


Once You See the Difference, the Names Make Sense

Promise.all() is collaborative.

Promise.race() is competitive.

That is not a perfect formal definition, but it is a good practical memory aid. One waits for the whole group. The other stops caring once the first result arrives.

When you keep that picture in mind, both APIs become much easier to use correctly.


Categories:

  1. Development
  2. Front‑End Development
  3. Guides
  4. JavaScript