Skip to content
JavaScript js async 4 min read

Promise Combinators

Real applications rarely await a single asynchronous task in isolation—you fetch several resources at once, race a request against a timeout, or kick off many jobs and report on each. The Promise class ships four static combinators that take an iterable of promises and return one new promise summarising the group. Choosing the right combinator is the difference between code that fails fast, code that tolerates partial failure, and code that finishes the moment the first result lands. This page covers Promise.all, Promise.allSettled, Promise.race, and Promise.any.

All four accept any iterable (typically an array) and run the supplied promises concurrently—the promises are already in flight before you pass them in; the combinator only decides how to aggregate their outcomes.

Promise.all — fail fast on the first rejection

Promise.all waits for every promise to fulfil, then resolves with an array of results in the same order as the input (regardless of which finished first). If any promise rejects, the returned promise rejects immediately with that reason—it does not wait for the rest. Use it when you need all the results and a single failure means the whole operation is invalid.

const urls = [
  "https://api.example.com/user",
  "https://api.example.com/posts",
  "https://api.example.com/settings",
];

const [user, posts, settings] = await Promise.all(
  urls.map((url) => fetch(url).then((r) => r.json()))
);

console.log(user, posts, settings);

Because the result order matches the input order, destructuring like this is safe even though the requests resolve in an unpredictable sequence.

Gotcha: Promise.all rejects on the first failure, but the other promises keep running—it does not cancel them. To actually abort in-flight fetch calls, wire up an AbortController and abort the remaining requests in a catch.

Promise.allSettled — collect every outcome

Promise.allSettled never rejects (short of a synchronous bug). It waits for every promise to settle—fulfilled or rejected—and resolves with an array of result objects. Each object has a status of "fulfilled" (with a value) or "rejected" (with a reason). Reach for it when partial success is acceptable and you want to inspect each result individually.

const results = await Promise.allSettled([
  Promise.resolve(42),
  Promise.reject(new Error("network down")),
  Promise.resolve("ok"),
]);

for (const r of results) {
  if (r.status === "fulfilled") console.log("✓", r.value);
  else console.log("✗", r.reason.message);
}

Output:

✓ 42
✗ network down
✓ ok

Promise.race — first to settle wins

Promise.race settles as soon as the first promise settles, adopting its state—fulfilled or rejected. The classic use case is imposing a timeout: race a real operation against a timer that rejects.

function timeout(ms) {
  return new Promise((_, reject) =>
    setTimeout(() => reject(new Error("Timed out")), ms)
  );
}

try {
  const data = await Promise.race([
    fetch("/slow-endpoint").then((r) => r.json()),
    timeout(3000),
  ]);
  console.log("got data", data);
} catch (err) {
  console.error(err.message); // "Timed out" if fetch took >3s
}

Tip: Because race settles on the first outcome, an early rejection wins over a later fulfilment. If a rejection should not abort the race, use Promise.any instead.

Promise.any — first to fulfil wins

Promise.any (ES2021) resolves with the value of the first promise to fulfil, ignoring rejections. It only rejects if every promise rejects, and when it does the reason is an AggregateError whose .errors array holds all the individual reasons. It is ideal for redundancy—query several mirrors and take whichever responds successfully first.

try {
  const fastest = await Promise.any([
    fetch("https://mirror-1.example.com/data"),
    fetch("https://mirror-2.example.com/data"),
    fetch("https://mirror-3.example.com/data"),
  ]);
  console.log("served by", fastest.url);
} catch (err) {
  // All mirrors failed
  console.error(err instanceof AggregateError, err.errors.length);
}

Comparison at a glance

CombinatorResolves whenRejects whenResolved valueRejected reason
Promise.allall fulfilany rejectsarray of values (input order)first rejection reason
Promise.allSettledall settleneverarray of {status, value/reason}
Promise.racefirst settles (fulfil)first settles (reject)first settled valuefirst settled reason
Promise.anyfirst fulfilsall rejectfirst fulfilled valueAggregateError

A quick way to remember it: all and allSettled wait for everyone; race and any finish early. Within each pair, all/race are sensitive to rejections, while allSettled/any shrug them off.

Best Practices

  • Reach for Promise.all when results are interdependent and any failure invalidates the whole batch—then handle the rejection in one place.
  • Prefer Promise.allSettled for fan-out work where partial success is useful, such as sending notifications or warming caches.
  • Use Promise.race for timeouts and cancellation patterns, pairing it with an AbortController to stop the work that lost the race.
  • Use Promise.any for redundancy across mirrors or fallbacks; remember to unwrap AggregateError.errors when everything fails.
  • Build the array of promises before passing it in—calling the async functions starts the work; the combinator only aggregates it.
  • Always attach a catch (or try/await) to combinators that can reject, otherwise you risk an unhandled rejection.
Last updated June 1, 2026
Was this helpful?