Knowledge Base/guides/When to Use Promise.all, Promise.allSettled, Promise.race, and Promise.any

When to Use Promise.all, Promise.allSettled, Promise.race, and Promise.any

Promise combinators are not four interchangeable ways to make async code "faster." They answer different questions. Use Promise.all when every task must succeed, Promise.allSettled when every result matters even after failures, Promise.race when the first settled Promise decides the outcome, and Promise.any when the first successful Promise is enough.

That is the whole map. The rest is learning not to lie to yourself about which outcome your program actually needs.

Contents

The quick decision table

Start with the result shape:

NeedUseFulfilled valueRejects when
All tasks must succeedPromise.allArray of fulfilled valuesAny input rejects
Every task must report its outcomePromise.allSettledArray of status objectsNever rejects because of input Promises
First task to settle decidesPromise.raceFirst fulfilled valueFirst settled Promise rejects
First success is enoughPromise.anyFirst fulfilled valueAll inputs reject

That table is more useful than memorizing examples. Concurrency bugs usually come from choosing the method by vibes instead of by failure behavior.

The naive approach waits too much

The beginner pattern is to put await in front of every async call:

async function loadDashboard() {
  const user = await fetchUser();
  const notifications = await fetchNotifications();
  const activity = await fetchActivity();

  return { user, notifications, activity };
}

That is correct only if each step depends on the previous step. If the three requests are independent, this code creates unnecessary waiting:

fetchUser starts
fetchUser finishes
fetchNotifications starts
fetchNotifications finishes
fetchActivity starts
fetchActivity finishes

The fix is not "sprinkle Promise.all everywhere." The fix is to start independent work together, then choose the combinator that matches your failure policy.

async function loadDashboard() {
  const [user, notifications, activity] = await Promise.all([
    fetchUser(),
    fetchNotifications(),
    fetchActivity(),
  ]);

  return { user, notifications, activity };
}

Now the requests overlap. The total wait is roughly the slowest request, not the sum of all three.

Use Promise.all when all tasks must succeed

Promise.all is the right tool when one failure makes the whole operation unusable.

async function loadAccountPage(accountId: string) {
  const [account, plan, permissions] = await Promise.all([
    fetchAccount(accountId),
    fetchPlan(accountId),
    fetchPermissions(accountId),
  ]);

  return { account, plan, permissions };
}

If fetchPermissions fails, the page probably should not pretend it has enough data. Promise.all rejects as soon as one input rejects.

The order of the returned values matches the order of the input array, not the order the Promises finished:

const [profile, settings] = await Promise.all([
  fetchProfile(),  // result goes into profile
  fetchSettings(), // result goes into settings
]);

That predictability is useful. The tradeoff is blunt failure behavior. One rejection rejects the whole combined Promise.

const result = await Promise.all([
  Promise.resolve("profile"),
  Promise.reject(new Error("settings failed")),
  Promise.resolve("activity"),
]);

// The await throws. You do not receive ["profile", ?, "activity"].

Use it when partial data is not acceptable.

Use Promise.allSettled when every outcome matters

Promise.allSettled waits for every input Promise to settle. It does not reject just because an input rejected. Instead, each item becomes a status object.

const results = await Promise.allSettled([
  fetchProfile(),
  fetchRecommendations(),
  fetchActivity(),
]);

for (const result of results) {
  if (result.status === "fulfilled") {
    console.log("Loaded:", result.value);
  } else {
    console.log("Failed:", result.reason);
  }
}

This is the right shape for independent optional work:

async function loadHomePage() {
  const [profile, recommendations, activity] = await Promise.allSettled([
    fetchProfile(),
    fetchRecommendations(),
    fetchActivity(),
  ]);

  return {
    profile:
      profile.status === "fulfilled" ? profile.value : null,
    recommendations:
      recommendations.status === "fulfilled" ? recommendations.value : [],
    activity:
      activity.status === "fulfilled" ? activity.value : [],
  };
}

That code is noisier than Promise.all, and that is the point. Partial failure has to be handled deliberately. Otherwise you get the classic dashboard where one flaky sidebar takes down the entire page. Elegant, in the same way a loose stair is elegant.

Use Promise.allSettled for batch jobs, optional panels, upload queues, validation across several independent fields, and reporting where failures are data.

Use Promise.race when the first settlement wins

Promise.race resolves or rejects with the first Promise that settles. Fulfillment and rejection both count.

const first = await Promise.race([
  fetchFromPrimaryRegion(),
  fetchFromBackupRegion(),
]);

If the primary request fulfills first, first is its value. If the backup request rejects first, the race rejects even if the primary request would have succeeded 20 milliseconds later.

That rejection behavior is why Promise.race is sharp. It answers "what settled first?" not "what succeeded first?"

A common timeout pattern uses Promise.race:

function timeout(ms: number) {
  return new Promise<never>((_, reject) => {
    setTimeout(() => {
      reject(new Error(`Timed out after ${ms}ms`));
    }, ms);
  });
}

async function loadWithTimeout() {
  return Promise.race([
    fetch("/api/report"),
    timeout(5000),
  ]);
}

This stops waiting after 5 seconds. It does not cancel the fetch request. The browser request can keep running in the background.

If you need real request cancellation, use AbortController with fetch instead of only racing against a timer.

Use Promise.any when the first success wins

Promise.any fulfills with the first fulfilled value. Rejections are ignored unless every input rejects.

const product = await Promise.any([
  fetchFromCdn("us-east"),
  fetchFromCdn("us-west"),
  fetchFromCdn("eu-central"),
]);

This is useful when you have redundant sources and only need one successful answer. A rejection from the first source should not doom the operation if another source succeeds.

If every input rejects, Promise.any rejects with an AggregateError:

try {
  const value = await Promise.any([
    Promise.reject(new Error("primary failed")),
    Promise.reject(new Error("backup failed")),
  ]);

  console.log(value);
} catch (error) {
  if (error instanceof AggregateError) {
    console.log(error.errors);
  }
}

The difference from Promise.race is not subtle:

InputsPromise.racePromise.any
Fast rejection, slow successRejectsFulfills with the slow success
Fast success, slow rejectionFulfillsFulfills
All rejectRejects with first rejectionRejects with AggregateError

Use Promise.any when failure is tolerable as long as one option works.

Common traps

Promise.all does not start Promises. Calling the async functions starts the work.

const tasks = [
  fetchUser(),
  fetchSettings(),
];

await Promise.all(tasks);

In that example, fetchUser() and fetchSettings() are called before Promise.all receives the array. Promise.all only waits for the Promises it is given.

Do not pass function references when you meant to start work:

await Promise.all([
  fetchUser,
  fetchSettings,
]);

That resolves immediately with the functions themselves. Slightly embarrassing. Also very normal.

Another trap: none of these methods limit concurrency. If you pass 500 fetches to Promise.all, you asked for 500 operations to begin.

await Promise.all(userIds.map((id) => fetchUser(id)));

That might be fine for 5 users. It is probably rude at 5,000. Use a queue or batch the work if the system on the other end matters, which it usually does.

Finally, remember that fetch() does not reject on HTTP status codes like 404 or 500. A failed HTTP response still fulfills the fetch Promise. Check response.ok yourself or read why fetch does not throw on 404 errors.

Key Takeaways

  • Use Promise.all when partial success is useless and one failure should reject the whole operation.
  • Use Promise.allSettled when failures are part of the result you need to inspect.
  • Use Promise.race when the first settled Promise wins, including fast rejections.
  • Use Promise.any when the first successful result wins and early failures should be ignored.
  • Promise combinators coordinate work. They do not cancel work, validate HTTP status codes, or limit concurrency for you.
Share this article:
javascriptpromisesasyncconcurrency