Does React use Promise.allSettled for parallel API calls, and how would that work internally?
No — React itself doesn't make API calls. The developer does, via fetch/axios/React Query. To parallelize, the developer uses Promise.all (fails fast) or Promise.allSettled (resolves to per-promise results regardless of failures). React Query handles parallelism via independent useQuery hooks that fire concurrently. Suspense + use() enables waterfall avoidance via parallel data dependencies.
Common misconception cleared up first.
React doesn't fetch
React renders. The developer chooses how to fetch. There's no built-in Promise.all behavior baked into React.
Promise.all vs Promise.allSettled
// Promise.all — rejects on first failure
const [a, b, c] = await Promise.all([
fetch('/a'), fetch('/b'), fetch('/c'),
]);
// Promise.allSettled — always resolves with an array of results
const results = await Promise.allSettled([
fetch('/a'), fetch('/b'), fetch('/c'),
]);
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value);
else console.error(r.reason);
});- Promise.all is right when you need ALL responses to proceed (a transaction).
- Promise.allSettled is right when partial success is acceptable (a dashboard).
Both run the requests in parallel — the difference is how rejections compose.
In React
function Dashboard() {
// Three independent queries fire in parallel — React Query handles parallelism.
const users = useQuery({ queryKey: ['users'], queryFn: fetchUsers });
const orders = useQuery({ queryKey: ['orders'], queryFn: fetchOrders });
const metrics = useQuery({ queryKey: ['metrics'], queryFn: fetchMetrics });
if (users.isLoading || orders.isLoading || metrics.isLoading) return <Spinner />;
return <Layout u={users.data} o={orders.data} m={metrics.data} />;
}Each useQuery fires its request immediately on mount. Three are in flight at once.
If you want allSettled semantics:
// React Query useQueries
const results = useQueries({
queries: [
{ queryKey: ['users'], queryFn: fetchUsers },
{ queryKey: ['orders'], queryFn: fetchOrders },
{ queryKey: ['metrics'], queryFn: fetchMetrics },
],
});
const anyError = results.some(r => r.isError);
const anyLoading = results.some(r => r.isLoading);Suspense + use()
React 19's use(promise) integrates Suspense with promises. With parallel fetching:
function Page({ usersPromise, ordersPromise }: Props) {
// Both promises started before the render — parallel.
const users = use(usersPromise);
const orders = use(ordersPromise);
return <Layout users={users} orders={orders} />;
}
// caller
const usersPromise = fetchUsers(); // started immediately
const ordersPromise = fetchOrders(); // started immediately, in parallel
<Suspense fallback={<Spinner />}>
<Page usersPromise={usersPromise} ordersPromise={ordersPromise} />
</Suspense>The key is starting the promises before the component renders — otherwise you get a waterfall.
Server Components + parallel fetches
// Server component
async function Page() {
const [users, orders] = await Promise.all([fetchUsers(), fetchOrders()]);
return <Layout users={users} orders={orders} />;
}The two fetches run in parallel on the server; the client receives the merged result.
Anti-patterns
- Waterfall fetching: each component fetches in useEffect using the previous result.
- Fix: lift fetches up, fetch in parallel, pass results down.
- await inside loops when items are independent:
``js // Bad for (const id of ids) await fetch(id); // Good await Promise.all(ids.map(id => fetch(id))); ``
- Promise.all when partial failure is fine: rejects the whole batch on one bad call.
When to use which
| Need | Choose |
|---|---|
| All must succeed (transaction) | Promise.all |
| Show partial data | Promise.allSettled |
| First responder wins (with cancel) | Promise.race |
| First successful (skip failures) | Promise.any |
| Many concurrent queries in React | useQueries |
| Server-side parallel data | RSC + Promise.all |
How parallelism works under the hood
The event loop kicks off all promises. fetch creates network requests that the browser sends concurrently (subject to HTTP/2 multiplexing or HTTP/1.1 connection limits — typically 6 per origin). All resolve into microtasks as their responses arrive. The aggregator (Promise.all etc.) collects them and resolves the outer promise.
There's no thread-level parallelism — it's I/O concurrency. The CPU work for parsing each response is still serialized on the main thread.
Follow-up questions
- •How does useQueries differ from multiple useQuery calls?
- •What's a waterfall fetch and how do you avoid it?
- •When would Promise.any beat Promise.all?
Common mistakes
- •Assuming React magically parallelizes — it doesn't; the developer composes promises.
- •Sequential await in a loop when items are independent.
- •Promise.all when partial failure should still show data.
Performance considerations
- •Parallelism cuts wall-clock time to max(durations) instead of sum(durations). Big win for independent fetches. HTTP/2 multiplexes; HTTP/1.1 caps. Server-side fetches are usually faster than client-side (closer to origin, no auth handshake on every request).
Edge cases
- •HTTP/1.1 limits to 6 connections per origin — beyond that requests queue.
- •AbortController to cancel in-flight when no longer needed.
- •Memory: many huge response bodies in flight can OOM low-memory devices.
Real-world examples
- •Dashboards (users + metrics + activity), product pages (item + reviews + recommendations), settings pages (profile + preferences + integrations). All benefit from parallel fetching.