Course: JavaScript Advanced
Lesson 10: Promise Methods
Level
Advanced
Estimated Reading Time
10–15 minutes
Goal
By the end of this lesson, the learner will be able to:
- Use
Promise.allSettledto handle multiple promises where some may fail independently - Use
Promise.raceto implement timeouts and first-response-wins patterns - Use
Promise.anyto implement redundant requests that succeed if at least one source works
Frontend Development Context
Promise.all is powerful but too blunt for many real scenarios — a single failure cancels everything. Real frontend applications often need more nuanced behavior: load all dashboard widgets but show each one as it resolves, or try two CDN sources and use whichever responds first, or set a timeout so a slow API doesn't freeze the UI indefinitely. These are the problems that Promise.allSettled, Promise.race, and Promise.any were designed to solve.
Knowing which promise combinator to reach for — and why — is the mark of a developer who thinks about reliability and user experience, not just the happy path. Choosing allSettled over all for a page with independently renderable sections can mean the difference between a partially-loaded but functional UI and a completely blank page when one API is slow.
Explanation
Promise.allSettled(promises) waits for every promise to either resolve or reject, and always fulfills (never rejects) with an array of result objects. Each object has a status field that is either "fulfilled" (with a value) or "rejected" (with a reason). Use allSettled when you want to know the outcome of every operation regardless of whether some fail — it never short-circuits.
Promise.race(promises) resolves or rejects as soon as the first promise in the array settles — in either direction. If the fastest promise resolves, race resolves with its value. If the fastest promise rejects, race rejects. The most practical use is implementing timeouts: race a real fetch against a Promise.reject that fires after a delay. The slower of the two is effectively ignored.
Promise.any(promises) is the opposite of Promise.all in a sense. It resolves as soon as the first promise resolves successfully, ignoring rejections. It only rejects if all promises reject, and the rejection reason is an AggregateError containing all individual errors. Use Promise.any for redundant sources — try multiple CDNs or API mirrors and use whichever responds first successfully.
The four combinators form a matrix: all needs all to succeed; allSettled waits for all regardless; race takes the first to settle (either direction); any takes the first to succeed. Choosing the right one is a design decision based on what failure modes are acceptable for the user experience.
One important nuance: all four combinators start all promises simultaneously. The difference is only in how they respond to their outcomes. There is no lazy evaluation — all requests begin the moment you pass the array to the combinator.
Code Example
This example demonstrates Promise.allSettled for a multi-widget dashboard, Promise.race for a timeout pattern, and Promise.any for trying redundant CDN sources.
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function fetchWithDelay(url, ms, shouldFail = false) {
return delay(ms).then(() => {
if (shouldFail) throw new Error(`Failed: ${url}`);
return { source: url, data: `content from ${url}` };
});
}
// --- Promise.allSettled: dashboard widgets ---
async function loadDashboard() {
const widgetRequests = [
fetchWithDelay("/api/sales", 100),
fetchWithDelay("/api/inventory", 150, true), // will fail
fetchWithDelay("/api/notifications", 120),
fetchWithDelay("/api/analytics", 200, true), // will fail
];
const results = await Promise.allSettled(widgetRequests);
results.forEach((result, i) => {
if (result.status === "fulfilled") {
console.log(`Widget ${i + 1} loaded:`, result.value.data);
} else {
console.warn(`Widget ${i + 1} failed:`, result.reason.message);
}
});
}
// --- Promise.race: fetch with timeout ---
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timed out after ${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}
async function loadWithTimeout() {
try {
const result = await withTimeout(
fetchWithDelay("/api/slow-data", 500), // takes 500ms
300 // timeout after 300ms
);
console.log("Loaded:", result.data);
} catch (err) {
console.error("Load failed:", err.message); // "Timed out after 300ms"
}
}
// --- Promise.any: try multiple CDN sources ---
async function loadAsset(filename) {
const cdnSources = [
fetchWithDelay(`https://cdn1.example.com/${filename}`, 300, true), // fails
fetchWithDelay(`https://cdn2.example.com/${filename}`, 200, true), // fails
fetchWithDelay(`https://cdn3.example.com/${filename}`, 250), // succeeds
];
try {
const result = await Promise.any(cdnSources);
console.log("Asset loaded from:", result.source);
} catch (err) {
// AggregateError: all sources failed
console.error("All CDNs failed:", err.errors.map(e => e.message));
}
}
loadDashboard();
loadWithTimeout();
loadAsset("bundle.js");
Code Explanation
loadDashboard uses Promise.allSettled to start all four widget requests simultaneously. Even though two of them fail, allSettled does not reject — it waits for all four to settle and returns an array of result descriptors. The forEach loop checks each result's status and either renders the widget or logs a warning. This approach allows a partial dashboard to render successfully even when some APIs are unavailable.
withTimeout wraps Promise.race to create a reusable timeout utility. It races the real request against a "time bomb" promise that rejects after ms milliseconds. If the fetch takes longer than ms, the timeout rejects first and race adopts that rejection. The fetch continues running in the background (since there's no AbortController here) but its result is ignored. In production, you'd add an AbortController to actually cancel the in-flight request when the timeout fires.
loadAsset uses Promise.any to try three CDN sources simultaneously. CDN1 and CDN2 both fail, but CDN3 succeeds. Promise.any ignores the two rejections and resolves with CDN3's value — the first successful result. If all three had failed, the catch block would receive an AggregateError containing all three individual errors, which can be inspected with err.errors.
Together these three patterns cover the most common multi-promise scenarios in production frontends: independently-failing-but-all-needed operations, timeout guardrails, and redundant source fallback.
Pattern Highlights
✅ Positive Pattern: Use
Promise.allSettledwhen you need to render a page with independently loadable sections — it lets the UI show what succeeded without being blocked by what failed.
⚠️ Neutral Note:
Promise.racewith a timeout does not cancel the losing promise — the fetch continues running after the timeout wins. Pair it withAbortControllerto actually stop the in-flight request and free resources.
❌ Negative Pattern: Do not use
Promise.allwhen some failures are acceptable — if one of five independent panel fetches fails, usingallmakes the whole page fail. UseallSettledand handle each result individually.
Common Mistakes
Mistake 1: Catching allSettled errors with a top-level try/catch
Promise.allSettled itself never rejects, so a top-level try/catch will never catch individual failures from it.
try {
const results = await Promise.allSettled([
fetchSomething(),
fetchSomethingElse(),
]);
// Individual failures are in results[i].reason, not caught here
} catch (err) {
console.error("This never runs for individual failures");
}
Check each result's status field inside the results array. The try/catch only catches errors thrown during the allSettled call itself, which is extremely rare.
Mistake 2: Using Promise.race for happy-path first-result
Promise.race resolves or rejects with the first to settle, not the first to succeed. If the fastest promise rejects, the race rejects even if others would have succeeded.
// If the fastest CDN fails first, race rejects — even if others succeed
const result = await Promise.race([cdn1, cdn2, cdn3]);
For first-success semantics, use Promise.any, which ignores rejections and only resolves on the first successful result.
Mistake 3: Forgetting that Promise.any rejects with AggregateError
Developers expect a normal Error object in the catch block but receive an AggregateError.
try {
await Promise.any([failingFetch1(), failingFetch2()]);
} catch (err) {
console.error(err.message); // "All promises were rejected"
// err.errors is the array of individual errors — often forgotten
}
Always inspect err.errors (an array) in Promise.any catch blocks to get the individual failure reasons from each promise.
Quick Recap
Promise.allSettledwaits for all promises and always fulfills with an array of result objects — use it when each operation is independent and partial failures are acceptable.Promise.raceresolves or rejects with the first promise to settle in either direction — the primary use case is implementing request timeouts.Promise.anyresolves with the first successful result and only rejects if all promises fail (with anAggregateError) — use it for redundant sources where any one working is sufficient.- All four combinators (
all,allSettled,race,any) start all promises simultaneously; the difference is only in how they respond to the outcomes.