Promise.allSettled, any, race: Picking the Right Combinator
A dashboard loads five widgets in parallel with Promise.all. The recommendations service hiccups. All five widgets show the error state - including the four whose requests succeeded perfectly. That's not a bug in all(); it's the wrong combinator for the job.
The four, in one table's worth of prose
all: resolves when every promise resolves, rejects on the first rejection. For operations that only make sense together (fetch user + fetch permissions before rendering anything). allSettled: always resolves, with a status report per promise. For independent operations. any: resolves with the first success, rejects only if all fail. For redundant sources. race: settles with whatever finishes first, success or failure. For timeouts and "first response wins".
allSettled: the dashboard fix
const [stats, orders, recs] = await Promise.allSettled([
fetchStats(), fetchOrders(), fetchRecommendations(),
]);
renderWidget('#stats', stats);
renderWidget('#orders', orders);
renderWidget('#recs', recs);
function renderWidget(sel, result) {
result.status === 'fulfilled'
? render(sel, result.value)
: renderError(sel, result.reason); // ONLY this widget degrades
}
Each result is {status: 'fulfilled', value} or {status: 'rejected', reason}. Four widgets render, one shows a retry button, nobody loses data they already had. This shape - independent parallel work, partial failure acceptable - is most parallel work in real apps, which makes allSettled the combinator I reach for most.
any: fallback sources
// First CDN that responds wins; only fails if ALL do
const config = await Promise.any([
fetch('https://cdn-a.example.com/config.json'),
fetch('https://cdn-b.example.com/config.json'),
fetch('/config.json'),
]).then(r => r.json());
When any() does reject, you get an AggregateError whose .errors array holds every individual failure - log them all, because "all three sources died" usually means something bigger than one flaky host.
race: mostly timeouts, and there's a better timeout now
// The classic
const data = await Promise.race([
fetchSlowReport(),
new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 8000)),
]);
Fine, but the losing fetch keeps running and the timer leaks. For fetch specifically, AbortSignal.timeout() actually cancels the request - use race for non-abortable things (waiting on a user action vs an animation, first of several workers).
One recurring mistake to unlearn: awaiting sequentially when you meant parallel. await a(); await b(); is a waterfall; start both first (const pa = a(), pb = b();) or use a combinator. The combinators only help if the requests actually overlap.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.