JavaScript Promise Patterns: all, race, any, and allSettled

Promise combinators let you coordinate multiple asynchronous operations in JavaScript with a single line of code. This article explains how Promise.all, Promise.race, Promise.any, and Promise.allSettled behave, when to use each one, and how to avoid the mistakes that often trip up developers.

Quick answer: Use Promise.all when every task must succeed, Promise.race when you want the first settled result, Promise.any when you want the first successful result, and Promise.allSettled when you need every outcome no matter what.

Difficulty: Intermediate

You'll understand this better if you know: how promises represent async results, how async/await works, and the difference between fulfillment and rejection.

1. What Are Promise Patterns?

Promise patterns are built-in methods that combine multiple promises into one result. They solve a common problem: when your code starts several asynchronous tasks at once, you need a predictable way to wait for them, react to the first one that finishes, or inspect every outcome.

These methods are often called promise combinators because they combine multiple async operations into one promise.

2. Why Promise Patterns Matter

Real applications often need to fetch multiple resources, run several checks in parallel, or provide a fallback when one service is slow. Promise patterns help you write that logic clearly instead of manually counting completions and tracking state.

They also make your code easier to reason about. Instead of nesting callbacks or writing custom bookkeeping, you can express your intent directly: wait for everything, use the fastest response, accept the first success, or collect all results.

3. Basic Syntax or Core Idea

All four methods accept an iterable, usually an array of promises. The return value is always a promise, but each method resolves according to a different rule.

Promise.all syntax

Use Promise.all when all results matter and a single failure should fail the whole operation.

const results = await Promise.all([promiseA, promiseB, promiseC]);

If every promise fulfills, the returned array preserves the original order of the input, not the completion order.

Promise.race syntax

Use Promise.race when the first settled outcome should win.

const winner = await Promise.race([promiseA, promiseB]);

The returned promise settles with the same value or reason as the first promise to settle.

Promise.any syntax

Use Promise.any when you want the first successful result and can ignore earlier failures.

const value = await Promise.any([promiseA, promiseB]);

If every promise rejects, the returned promise rejects with an AggregateError.

Promise.allSettled syntax

Use Promise.allSettled when you need the result of every promise, even if some fail.

const outcomes = await Promise.allSettled([promiseA, promiseB]);

The result is an array of objects describing whether each promise was fulfilled or rejected.

4. Step-by-Step Examples

Example 1: Waiting for every request with Promise.all

Suppose you need a profile, notifications, and settings before rendering a dashboard. You can start the requests together and wait until all of them finish.

const profilePromise = fetch("/api/profile").then(response => response.json());
const notificationsPromise = fetch("/api/notifications").then(response => response.json());
const settingsPromise = fetch("/api/settings").then(response => response.json());

const [profile, notifications, settings] = await Promise.all([
  profilePromise,
  notificationsPromise,
  settingsPromise
]);

This pattern is useful when every value is required. If one request fails, the whole operation rejects immediately.

Example 2: Using the fastest response with Promise.race

Imagine a request that may be served from a cache or from the network. You can race them and take the first one to settle.

const cachePromise = readFromCache("user:42");
const networkPromise = fetch("/api/users/42").then(response => response.json());

const result = await Promise.race([
  cachePromise,
  networkPromise
]);

The first settled promise wins, even if that outcome is a rejection. That makes Promise.race useful for timeouts and fallback strategies, but it also means you must be careful about failures.

Example 3: Accepting the first success with Promise.any

If you have multiple mirrors for the same resource, you may want the first successful response and ignore early failures.

const mirrorA = fetch("https://mirror-a.example.com/data.json").then(response => response.json());
const mirrorB = fetch("https://mirror-b.example.com/data.json").then(response => response.json());
const mirrorC = fetch("https://mirror-c.example.com/data.json").then(response => response.json());

const data = await Promise.any([mirrorA, mirrorB, mirrorC]);

This is different from Promise.race. A rejected promise does not win unless every promise rejects.

Example 4: Collecting all outcomes with Promise.allSettled

When you need to report which tasks succeeded and which failed, Promise.allSettled is the safest choice.

const tasks = [
  fetch("/api/a"),
  fetch("/api/b"),
  fetch("/api/c")
];

const results = await Promise.allSettled(tasks);

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

The result array gives you one object per input promise, making it easy to display partial success.

5. Practical Use Cases

These patterns appear in dashboards, search, file processing, monitoring tools, and reliability-sensitive applications.

6. Common Mistakes

Mistake 1: Expecting Promise.all to keep successful results after one failure

Promise.all rejects as soon as any input promise rejects. Many beginners expect it to return the successful values and ignore the failure, but that is what Promise.allSettled is for.

Problem: If one request fails, the whole Promise.all call rejects and you do not get a partial array of fulfilled values.

const profile = fetch("/api/profile").then(response => response.json());
const settings = fetch("/api/settings").then(response => response.json());
const badRequest = Promise.reject(new Error("settings unavailable"));

const data = await Promise.all([profile, settings, badRequest]);

Fix: Use Promise.allSettled when you want every outcome, or catch errors before combining them if a fallback is acceptable.

const results = await Promise.allSettled([profile, settings, badRequest]);

const successfulValues = results
  .filter(result => result.status === "fulfilled")
  .map(result => result.value);

The corrected version works because it is designed for mixed success and failure.

Mistake 2: Using Promise.race when you really want the first success

Promise.race does not ignore rejections. If the fastest promise rejects, the entire race rejects immediately.

Problem: A slow successful request cannot save the result if a faster request fails first.

const fastFail = Promise.reject(new Error("cache miss"));
const slowSuccess = new Promise(resolve => setTimeout(() => resolve("network data"), 100));

const value = await Promise.race([fastFail, slowSuccess]);

Fix: Use Promise.any when you need the first fulfilled result, not the first settled result.

const value = await Promise.any([fastFail, slowSuccess]);

The corrected version waits for the first success instead of failing on the first rejection.

Mistake 3: Ignoring Promise.any rejection behavior

Some developers assume Promise.any never rejects because it can skip failures. In reality, it rejects when all input promises reject, and the error is an AggregateError.

Problem: If every promise fails, the catch block receives an aggregate of all reasons, not a normal single error message.

try {
  await Promise.any([
    Promise.reject(new Error("A failed")),
    Promise.reject(new Error("B failed"))
  ]);
} catch (error) {
  console.log(error.message);
}

Fix: Check for AggregateError and inspect its errors array when you need the reasons for failure.

try {
  await Promise.any([
    Promise.reject(new Error("A failed")),
    Promise.reject(new Error("B failed"))
  ]);
} catch (error) {
  if (error instanceof AggregateError) {
    for (const reason of error.errors) {
      console.error(reason.message);
    }
  }
}

The corrected version handles the real shape of the failure.

7. Best Practices

Practice 1: Start independent promises before awaiting

To get real concurrency, create the promises first and await the combined result afterward. If you await each request one by one, you turn parallel work into serial work.

const userPromise = fetch("/api/user");
const postsPromise = fetch("/api/posts");

const [userResponse, postsResponse] = await Promise.all([userPromise, postsPromise]);

This pattern reduces total wait time because both requests run at the same time.

Practice 2: Choose the combinator that matches your failure policy

Your error strategy should drive the method you choose. If the task fails when any part fails, use Promise.all. If partial results are acceptable, use Promise.allSettled. If one success is enough, use Promise.any.

const chosen = await Promise.allSettled([taskA, taskB, taskC]);

Matching the method to the business rule makes code easier to maintain and less surprising.

Practice 3: Handle rejected outcomes explicitly

With Promise.allSettled, do not assume every result has a value. Read the status field before touching value or reason.

const results = await Promise.allSettled([taskA, taskB]);

for (const result of results) {
  if (result.status === "fulfilled") {
    console.log(result.value);
  }
}

This avoids undefined access and keeps your success and failure paths clear.

8. Limitations and Edge Cases

That last point matters in browser and server code alike. If you are racing fetch requests, the slower ones may still consume bandwidth unless you explicitly abort them with a separate cancellation mechanism.

9. Practical Mini Project

Here is a small dashboard helper that loads three widgets in parallel, accepts the first successful fallback source for featured content, and records which widgets failed without breaking the page.

async function loadDashboard() {
  const statsPromise = fetch("/api/stats").then(response => response.json());
  const alertsPromise = fetch("/api/alerts").then(response => response.json());
  const featuredA = fetch("/api/featured-a").then(response => response.json());
  const featuredB = fetch("/api/featured-b").then(response => response.json());

  const [statsResult, alertsResult] = await Promise.allSettled([
    statsPromise,
    alertsPromise
  ]);

  let featured;
  try {
    featured = await Promise.any([featuredA, featuredB]);
  } catch {
    featured = null;
  }

  return {
    stats: statsResult.status === "fulfilled" ? statsResult.value : null,
    alerts: alertsResult.status === "fulfilled" ? alertsResult.value : null,
    featured
  };
}

This example shows a realistic combination of promise patterns: allSettled for partial data, any for fallback content, and normal promise chaining for JSON parsing.

10. Key Points

11. Practice Exercise

Expected output: The Promise.all version should log an array only when all promises fulfill. The Promise.allSettled version should log one result object per promise, including the rejection.

Hint: Use setTimeout inside new Promise to control the timing.

const makePromise = (label, delay, shouldReject = false) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (shouldReject) {
        reject(new Error(`${label} failed`));
      } else {
        resolve(label);
      }
    }, delay);
  });
};

const a = makePromise("A", 300);
const b = makePromise("B", 100, true);
const c = makePromise("C", 200);

try {
  const allResults = await Promise.all([a, b, c]);
  console.log(allResults);
} catch (error) {
  console.error(error.message);
}

const settledResults = await Promise.allSettled([a, b, c]);
console.log(settledResults);

12. Final Summary

Promise patterns give you a compact, reliable way to coordinate multiple asynchronous operations in JavaScript. The key is to match the method to the behavior you want: wait for everything with Promise.all, use the first settled value with Promise.race, accept the first success with Promise.any, or inspect every outcome with Promise.allSettled.

Once you understand the difference between settlement and fulfillment, these methods become easy to choose correctly. They are especially useful for API requests, fallback systems, batch processing, and partial-failure reporting.

Next, practice combining these methods with async/await and timed requests so you can recognize which pattern fits a given problem quickly.