AlgoInsight

Free lesson · JavaScript Basic to Advance

Combining Promises: all, race, allSettled, any

Easy5 min read

Running Multiple Async Operations Together

The previous Topics covered one Promise at a time. But real applications often need to run several Promises together — sometimes in parallel, sometimes waiting for whichever finishes first, sometimes accepting partial failures. JavaScript provides four built-in combinators for these cases.

function delay(ms, value) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

const p1 = delay(50, "A");
const p2 = delay(100, "B");
const p3 = delay(75, "C");

// All four combinators take an array of Promises
Promise.all([p1, p2, p3]).then(r => console.log("all:", r));
Promise.race([p1, p2, p3]).then(r => console.log("race:", r));
Promise.allSettled([p1, p2, p3]).then(r => console.log("allSettled:", r));
Promise.any([p1, p2, p3]).then(r => console.log("any:", r));

Each combinator handles "multiple Promises" differently. The key to using them correctly is knowing which one matches what you actually need.

This Topic covers all four, with realistic examples and the patterns for each.

Promise.all — All Must Succeed

Promise.all(promises) returns a Promise that:

function fetchUser(id) {
  return new Promise(resolve => setTimeout(() => resolve({ id, name: `User${id}` }), 100));
}

async function loadDashboard() {
  // Fetch three things in parallel
  const [user, settings, notifications] = await Promise.all([
    fetchUser(1),
    fetchUser(2),
    fetchUser(3)
  ]);

  console.log({ user, settings, notifications });
}

loadDashboard();

This is the most-used combinator. When you have multiple independent operations and you need all of them to succeed, Promise.all is the right tool. The destructuring on the left side maps each result to its variable cleanly.

The critical detail: Promise.all is fail-fast. The moment one Promise rejects, the combined Promise rejects too — without waiting for the others. The other operations still complete in the background, but their results are discarded.

function delay(ms, value) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

function failAfter(ms, reason) {
  return new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
}

async function example() {
  try {
    const results = await Promise.all([
      delay(100, "fast"),
      failAfter(50, "quick failure"),
      delay(200, "slow")
    ]);
    console.log(results);
  } catch (err) {
    console.log("Failed:", err.message);
    // The Promise rejected after 50ms with "quick failure"
    // The other two Promises still completed in the background, but we don't see their results
  }
}

example();

Use Promise.all when partial failure isn't acceptable — when you genuinely need all the operations to succeed before you can continue.

Interactive animation: One rejection at 50ms sinks the whole Promise.all - the two successful requests still complete, unread.

Promise.race — First to Settle Wins

Promise.race(promises) returns a Promise that settles with the result of the first input Promise to settle — fulfilled or rejected. Whichever finishes first determines the outcome.

function delay(ms, value) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

async function example() {
  const result = await Promise.race([
    delay(100, "slow"),
    delay(50, "fast"),
    delay(200, "slowest")
  ]);

  console.log("Race winner:", result);
}

example();

Promise.race is useful for timeouts. The classic pattern: race an operation against a delay, and whichever finishes first wins:

function fetchWithTimeout(url, ms) {
  const fetchPromise = new Promise(resolve => {
    setTimeout(() => resolve(`Data from ${url}`), 200);
  });

  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error("Timeout")), ms);
  });

  return Promise.race([fetchPromise, timeoutPromise]);
}

async function test() {
  try {
    const fast = await fetchWithTimeout("api/data", 500);   // 500ms timeout, request takes 200ms
    console.log("Fast:", fast);
  } catch (err) {
    console.log("Fast error:", err.message);
  }

  try {
    const slow = await fetchWithTimeout("api/data", 100);   // 100ms timeout, request takes 200ms
    console.log("Slow:", slow);
  } catch (err) {
    console.log("Slow error:", err.message);
  }
}

test();

The first call succeeds because the fetch (200ms) finishes before the timeout (500ms). The second call times out because the timeout (100ms) fires before the fetch (200ms) completes.

Promise.race is also useful when you have redundant sources and want whichever responds first — multiple servers for the same data, fallback strategies, etc.

Promise.allSettled — Wait for All, Regardless of Outcome

Promise.allSettled(promises) returns a Promise that resolves when every input Promise has settled, with an array describing what happened to each:

function delay(ms, value) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

function failAfter(ms, reason) {
  return new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
}

async function example() {
  const results = await Promise.allSettled([
    delay(50, "success 1"),
    failAfter(100, "failure A"),
    delay(150, "success 2"),
    failAfter(75, "failure B")
  ]);

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

example();

allSettled is the "no fail-fast" version of Promise.all. It always waits for everything to finish, and gives you a structured report of what happened.

Each entry in the result array has:

This is useful when partial failure is acceptable and you want to know which operations succeeded and which failed. Loading a dashboard where some widgets might fail, sending notifications where some users might be unreachable, running independent batch jobs — these are all scenarios for allSettled.

Interactive animation: Promise.allSettled reports every verdict - fulfilled values and the rejected reason side by side, and nothing sinks.

Promise.any — First Success Wins

Promise.any(promises) returns a Promise that resolves with the first successful result. It ignores rejections unless every input Promise rejects, in which case it rejects with an AggregateError.

function delay(ms, value) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

function failAfter(ms, reason) {
  return new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
}

async function example() {
  const result = await Promise.any([
    failAfter(50, "first failure"),
    delay(150, "first success"),
    delay(100, "would have been second")
  ]);

  console.log("Any:", result);
}

example();

Promise.any resolves with "first success" after 150ms because that's the first operation to succeed — it ignores the earlier failure.

This is useful for redundant operations: try several mirror servers, several APIs, several strategies, and use the first one that works. It's like Promise.race but tolerant of failures.

The difference from Promise.race:

graph TD
    A[Multiple Promises] --> B[Promise.all<br/>all must succeed]
    A --> C[Promise.race<br/>first to settle wins]
    A --> D[Promise.allSettled<br/>wait for all, all results]
    A --> E[Promise.any<br/>first success wins]

    B --> F[Fail-fast on first error]
    C --> G[Doesn't distinguish success/failure]
    D --> H[Never rejects from the combinator]
    E --> I[Tolerant of failures]

    style B fill:#7c3aed,color:#fff
    style C fill:#3b82f6,color:#fff
    style D fill:#10b981,color:#fff
    style E fill:#f59e0b,color:#fff
This diagram compares the four Promise combinators. Promise.all requires all input Promises to succeed and fails fast on the first error. Promise.race resolves or rejects based on whichever input Promise settles first, without distinguishing success from failure. Promise.allSettled waits for every Promise to finish regardless of outcome and returns a report of all results, never rejecting itself. Promise.any waits for the first success and only rejects if every input Promise rejects.

Choosing the Right Combinator

A practical decision framework:

This decision should be made deliberately. Reaching for Promise.all by default is common, and it's often wrong — it fails the whole operation on a single error when you might have been fine with partial results.

Common Pitfalls

Warning: The Promise combinator traps.

Using Promise.all when you want allSettled. If partial failure is fine, Promise.all will give you a worse experience — failing the whole operation on the first error. Use allSettled when you want completion regardless of failures.

Using Promise.race when you want Promise.any. race is sensitive to fast failures — if one operation fails quickly, you get an error even if another would have succeeded. any is the right tool when you specifically want a success.

Forgetting the array. All four combinators take an array of Promises. Passing them as separate arguments doesn't work — Promise.all(p1, p2) is a TypeError.

Mixing non-Promises in the array. All four combinators wrap non-Promise values in Promise.resolve automatically. So Promise.all([5, somePromise]) works, but it's clearer to be explicit if you're mixing.

Interactive animation: Promise.any ignores a mirror that errors at 40ms and returns the first success - race would have delivered the error.

Why This Matters in Interviews

Interview: Promise combinator questions are common at mid-to-senior levels. "What's the difference between Promise.all and Promise.allSettled?" The expected answer covers fail-fast vs wait-for-all. "When would you use Promise.race?" The expected example is timeouts.

Implementation challenges sometimes come up: "Implement Promise.all from scratch." The answer involves a counter and an array that fills as each Promise resolves. This is a great interview-prep exercise.

Topic 9.12 — Interview Lens: Promise and async/await Patterns — covers the deeper treatment, including the implementation challenges.

Try It

Exercise: Implement your own myAll that mimics Promise.all:

  1. Takes an array of Promises
  2. Returns a single Promise
  3. Resolves with an array of values in the same order as the input when all Promises resolve
  4. Rejects with the first error if any Promise rejects
function myAll(promises) {
  // your code
}

myAll([
  Promise.resolve("a"),
  Promise.resolve("b"),
  Promise.resolve("c")
]).then(r => console.log(r));   // ["a", "b", "c"]

Hint: you'll need a counter, a results array, and the trick is making sure the results stay in the original order (not the order of resolution). This is a classic interview exercise — once you can do it cleanly, you've demonstrated real understanding of Promises.

Back to JavaScript Basic to Advance