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.
- Promise.all waits for all promises to fulfill.
- Promise.race settles as soon as the first promise settles, whether that is fulfillment or rejection.
- Promise.any fulfills as soon as one promise fulfills.
- Promise.allSettled waits for every promise to finish and reports each result.
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
- Load several API resources in parallel before rendering a view.
- Use a timeout by racing a request against a timer promise.
- Query redundant services and accept the first successful response.
- Run batch jobs where every result must be reported, even failures.
- Process multiple uploads and show a per-file success or failure summary.
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
- Promise.race does not mean “fastest success”; it means “first settled promise,” which can be a rejection.
- Promise.any requires at least one fulfillment and throws AggregateError if all promises reject.
- Promise.all keeps the order of inputs, not the order in which promises finish.
- Passing plain values instead of promises usually works because these methods wrap them with Promise.resolve, but that can hide mistakes in async logic.
- None of these methods cancel the promises that lose the race or are no longer needed; the underlying work may continue unless you add cancellation yourself.
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
- Promise.all is for “all must succeed.”
- Promise.race is for “first settled wins.”
- Promise.any is for “first fulfilled wins.”
- Promise.allSettled is for “show me every outcome.”
- The returned results preserve input order, even when completion order differs.
- These methods do not cancel slower promises automatically.
11. Practice Exercise
- Create three promises that resolve after different delays.
- Use Promise.all to wait for them and print the results in input order.
- Then change one promise to reject and compare the behavior of Promise.all and Promise.allSettled.
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.