Skip to content
Carl Victor Fontanos
Carl Victor Fontanos

Carl Victor Fontanos

Software Engineer

I build web applications and share what I learn along the way.

© 2026

Promise.allSettled, any, race: Picking the Right Combinator

C
Carlo Fontanos
· 2 min read

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.

C
Written by Carlo Fontanos

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.

Message sent!

Your details are only used to reply to you.

Keep reading