Promise.allSettled / Promise.any / Promise.race
debt(d7/e5/b3/t8)
Closest to 'only careful code review or runtime testing' (d7). No detection_hints.tools specified; default JavaScript linters (ESLint) don't flag Promise combinator misuse by default. The wrong combinator is syntactically valid and only fails at runtime under specific conditions (e.g., when a promise rejects in Promise.all() or when all promises reject in Promise.any()). Runtime testing or code review is needed to catch these mistakes.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix suggests replacing the combinator (allSettled vs all vs any vs race), but this often requires changes to how results are handled: Promise.all() → allSettled() means refactoring the catch block and result inspection logic. Adding AbortController for proper race() cancellation spans initialization, cleanup, and error handling. Not a one-line swap; typically requires updates to result processing across several lines or methods.
Closest to 'localised tax' (b3). The choice of Promise combinator is typically isolated within individual async functions or utility helpers. It doesn't impose a system-wide tax unless the same anti-pattern is repeated across many components. Once chosen, it affects only that specific operation's error handling and result processing, not the broader architecture or all future maintainers uniformly.
Closest to 'serious trap' (t7). The canonical misconception is that Promise.race() cancels losing promises — it does not. This contradicts intuition from similar concepts (e.g., goroutine cancellation patterns, some async libraries). Additionally, Promise.allSettled() never rejects, which contradicts the behavior of Promise.all(). The difference between any() and race() is also a behavioral trap: which combinator to use for timeouts vs. first-success. Competent developers unfamiliar with Promise semantics will guess wrong about cancellation and rejection behavior.
Also Known As
TL;DR
Explanation
Promise.all() is the most known combinator — it resolves when all promises resolve and rejects immediately on the first rejection (fail-fast). Three others cover different patterns: Promise.allSettled() (ES2020) waits for all promises to settle and returns an array of {status, value/reason} objects — none are skipped on failure. Promise.any() (ES2021) resolves with the first successful promise, only rejecting if all promises reject (with an AggregateError). Promise.race() resolves or rejects with whichever promise settles first — useful for timeouts. Combining these correctly depends entirely on whether you want fail-fast, fail-never, or first-success semantics.
Common Misconception
Why It Matters
Common Mistakes
- Using Promise.all() for independent operations where partial success is acceptable — one failure kills everything unnecessarily.
- Not handling AggregateError from Promise.any() — when all promises reject, any() throws AggregateError containing all rejection reasons; catch and handle it.
- Race condition with Promise.race() and side effects — the losing promises continue running and may modify shared state after the winner has already returned.
- Forgetting that allSettled() never rejects — you must inspect each result's status field; the outer await will always succeed.
Code Examples
// ❌ Promise.all() fails-fast — one error aborts everything
const [user, orders, recommendations] = await Promise.all([
fetchUser(id), // If this fails...
fetchOrders(id), // ...these are abandoned
fetchRecommendations(id), // ...and this too
]);
// User sees blank page instead of partial content
// ❌ Race timeout without cancellation — fetch continues in background
const result = await Promise.race([
fetch('/api/data'),
new Promise((_, reject) => setTimeout(() => reject('Timeout'), 3000)),
]);
// ✅ allSettled — partial failures show graceful degradation
const results = await Promise.allSettled([
fetchUser(id),
fetchOrders(id),
fetchRecommendations(id),
]);
const [user, orders, recs] = results.map(r =>
r.status === 'fulfilled' ? r.value : null
);
// Page renders with whatever data loaded successfully
// ✅ Race timeout with AbortController — actually cancels the request
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 3000);
try {
const result = await fetch('/api/data', { signal: controller.signal });
clearTimeout(timeout);
return await result.json();
} catch (e) {
if (e.name === 'AbortError') throw new Error('Request timed out');
throw e;
}
// ✅ any() — first working CDN wins
const image = await Promise.any([
fetch('https://cdn1.example.com/img.jpg'),
fetch('https://cdn2.example.com/img.jpg'),
]);