Skip to content
Node.js nd async 4 min read

Promise Combinators: all, race, allSettled, any

Real Node.js applications rarely await a single promise at a time. You fetch from several APIs, query multiple tables, or read a batch of files — and you want them to run concurrently rather than one after another. Promise combinators are the standard library’s tools for coordinating a group of promises and deciding how their collective result should be shaped. Picking the right one is the difference between fast, resilient code and code that silently swallows errors or stalls on a single slow dependency.

The four combinators at a glance

All four are static methods on the global Promise object. They take an iterable of promises (or plain values, which are treated as already-resolved) and return a single promise. What differs is when that promise settles and what it settles with.

CombinatorResolves whenRejects whenResult value
Promise.allall fulfillany rejects (fail-fast)array of fulfilled values
Promise.allSettledall settlenever rejectsarray of {status, value/reason}
Promise.racefirst settles (fulfill or reject)first settles with a rejectionthe first settled value/reason
Promise.anyfirst fulfillsall rejectfirst fulfilled value / AggregateError

A key point that trips people up: combinators do not start anything. The promises you pass in are already running the moment they were created. The combinator only observes them, so concurrency comes from creating all the promises up front and then awaiting the combinator.

Promise.all — fail fast on the happy path

Promise.all is the workhorse. Use it when you need every operation to succeed and you want their results as an ordered array. Order is preserved by position in the input, not by completion time. If any input promise rejects, Promise.all rejects immediately with that first reason — the others keep running but their results are discarded.

async function loadDashboard(userId) {
  const [profile, orders, notifications] = await Promise.all([
    fetch(`https://api.example.com/users/${userId}`).then((r) => r.json()),
    fetch(`https://api.example.com/users/${userId}/orders`).then((r) => r.json()),
    fetch(`https://api.example.com/users/${userId}/notifications`).then((r) => r.json()),
  ]);

  return { profile, orders, notifications };
}

const data = await loadDashboard(42);
console.log(`Loaded ${data.orders.length} orders for ${data.profile.name}`);

Output:

Loaded 3 orders for Ada Lovelace

Warning: Because Promise.all is fail-fast, a single rejected promise discards the values of siblings that already succeeded. If you need those partial results, reach for Promise.allSettled instead.

Promise.allSettled — collect every outcome

When you want a complete report regardless of individual failures, use Promise.allSettled. It never rejects. Instead it resolves with an array of result descriptors: { status: "fulfilled", value } or { status: "rejected", reason }. This is ideal for batch jobs where one bad item shouldn’t sink the whole operation.

import { readFile } from "node:fs/promises";

const files = ["config.json", "missing.json", "data.json"];

const results = await Promise.allSettled(
  files.map((name) => readFile(name, "utf8")),
);

for (const [i, result] of results.entries()) {
  if (result.status === "fulfilled") {
    console.log(`OK   ${files[i]} (${result.value.length} bytes)`);
  } else {
    console.log(`FAIL ${files[i]}: ${result.reason.code}`);
  }
}

Output:

OK   config.json (128 bytes)
FAIL missing.json: ENOENT
OK   data.json (512 bytes)

Promise.race — first to settle wins

Promise.race settles as soon as the first input promise settles — whether it fulfills or rejects. The classic use case is imposing a timeout: race the real work against a promise that rejects after N milliseconds.

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

async function fetchWithTimeout(url, ms = 2000) {
  return Promise.race([fetch(url), timeout(ms)]);
}

try {
  const res = await fetchWithTimeout("https://api.example.com/slow", 500);
  console.log("Status:", res.status);
} catch (err) {
  console.error(err.message);
}

Output:

Timed out after 500ms

Tip: For cancelling the underlying work (not just ignoring it), prefer an AbortController with AbortSignal.timeout(). Promise.race abandons the loser but does not stop it — the fetch keeps running in the background.

Promise.any — first success wins

Promise.any is the optimistic counterpart to race. It ignores rejections and resolves with the value of the first promise to fulfill. It only rejects if all inputs reject, in which case it throws an AggregateError whose .errors array holds every individual reason. This is perfect for querying redundant mirrors or fallbacks where you just need one to work.

const mirrors = [
  "https://eu.cdn.example.com/file.bin",
  "https://us.cdn.example.com/file.bin",
  "https://ap.cdn.example.com/file.bin",
];

try {
  const res = await Promise.any(mirrors.map((url) => fetch(url)));
  console.log("Fastest mirror responded:", res.url);
} catch (err) {
  // err is an AggregateError
  console.error(`All mirrors failed (${err.errors.length} errors)`);
}

Output:

Fastest mirror responded: https://us.cdn.example.com/file.bin

Choosing the right combinator

A quick decision guide:

  • Need all results and want to stop on the first failure → Promise.all.
  • Need all outcomes including failures, never throwing → Promise.allSettled.
  • Need the first to settle, e.g. a timeout guard → Promise.race.
  • Need the first success, with fallbacks → Promise.any.

CommonJS users: all four combinators are part of the JavaScript language, not a module, so they work identically with require-based code — there is nothing to import.

Best Practices

  • Create all promises before awaiting the combinator so they run concurrently; awaiting in a for loop serializes them and defeats the purpose.
  • Use Promise.allSettled for batch processing where partial success is acceptable, then filter on status to separate wins from failures.
  • Always attach error handling to Promise.all — an unhandled rejection from one promise will surface as the combinator’s rejection.
  • Pair Promise.race timeouts with an AbortController so the losing operation is actually cancelled, not just orphaned.
  • Inspect AggregateError.errors when Promise.any rejects to log why every option failed, rather than swallowing the failure.
  • Cap fan-out concurrency (with a pool or a library like p-limit) when mapping over large arrays, so you don’t open thousands of sockets at once.
Last updated June 14, 2026
Was this helpful?