Promise.all() is the default tool for running independent async operations concurrently, but its all-or-nothing failure behavior has real design implications for production code that this lesson explores in depth.
1Promise.all() Deep Dive | JavaScript Tutorial - In-Depth Guide Part 1
Promise.all() runs every promise concurrently and resolves with an array of results in the exact same order the promises were passed in — regardless of which one finishes first.
const [fast, slow] = await Promise.all([
delay(100).then(() => 'fast'),
delay(500).then(() => 'slow'),
]);
// fast resolves first, but array order is still [fast, slow]Order Is Preserved
2Promise.all() Deep Dive | JavaScript Tutorial - In-Depth Guide Part 2
Promise.all() rejects the instant any single promise rejects — even if the other promises would have eventually succeeded, their results are simply discarded.
await Promise.all([
fetchUser(), // succeeds
fetchOrders(), // rejects!
fetchStats(), // would have succeeded, but result is lost
]);
// throws — fetchStats() result never seenFail-Fast Rejection
3Promise.all() Deep Dive | JavaScript Tutorial - In-Depth Guide Part 3
The other promises in a rejected Promise.all() are NOT cancelled — they keep running in the background even though their results are discarded.
// Even after Promise.all() rejects, fetchStats() below
// keeps running to completion in the background:
fetchStats().then(() => console.log('still finished!'));Other Promises Aren't Cancelled
4Promise.all() Deep Dive | JavaScript Tutorial - In-Depth Guide Part 4
Promise.all() runs operations concurrently, not sequentially — the total time is roughly the duration of the slowest operation, not the sum of all of them.
// Sequential: ~3 seconds total
await task1(); await task2(); await task3();
// Concurrent: ~1 second total (if each takes ~1s)
await Promise.all([task1(), task2(), task3()]);Concurrency, Not Sum
5Promise.all() Deep Dive | JavaScript Tutorial - In-Depth Guide Part 5
When partial failure should be tolerated, wrap individual promises so they never reject, converting failures into regular result values Promise.all() can still collect.
const safe = (p) => p.then((value) => ({ ok: true, value }), (error) => ({ ok: false, error }));
const results = await Promise.all([safe(fetchA()), safe(fetchB())]);Tolerating Partial Failure
6Step-by-Step Breakdown
Promise.all() runs every promise concurrently and resolves with an array of results in the exact same order the promises were passed in — regardless of which one finishes first.
Checkpoint: If the second promise passed to Promise.all() resolves before the first one, does the result array still list them in the original input order?
- →Yes, order always matches the input array
- →No, results are ordered by completion time
Promise.all() rejects the instant any single promise rejects — even if the other promises would have eventually succeeded, their results are simply discarded.
The other promises in a rejected Promise.all() are NOT cancelled — they keep running in the background even though their results are discarded.
Checkpoint: When Promise.all() rejects because one promise failed, are the other still-pending promises cancelled?
- →Yes, they are immediately cancelled
- →No, they keep running; JS promises have no built-in cancellation
Promise.all() runs operations concurrently, not sequentially — the total time is roughly the duration of the slowest operation, not the sum of all of them.
When partial failure should be tolerated, wrap individual promises so they never reject, converting failures into regular result values Promise.all() can still collect.
Next, we'll explore 'Promise.allSettled() Deep Dive'.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Show One Unified Loading State While Promise.all() Is Pending
Since Promise.all() only settles once every operation finishes, tie a single accessible loading indicator (with aria-busy) to the whole Promise.all() call rather than showing separate, confusing partial-loading states for each individual request.
SEO Implications
- 1
Concurrent Data Fetching Improves Server-Rendering Speed
Using Promise.all() to fetch multiple required data sources concurrently during server-side rendering reduces Time to First Byte compared to sequential awaits, which is a positive factor for Core Web Vitals and search ranking.
Best Practices
Use Promise.all() Only When Every Result Is Truly Required
If losing one operation's result should not invalidate the others, either use Promise.allSettled() or wrap individual promises with a safe-result pattern instead.
Start All Promises Before Awaiting Any of Them
Passing already-started promises into Promise.all() (rather than awaiting them one by one first) is what actually achieves concurrency — awaiting each individually before building the array serializes them again.
Frequent Bugs
Accidentally serializing supposedly-parallel work by awaiting each promise individually before passing them to Promise.all(), e.g. `Promise.all([await fetchA(), await fetchB()])`.
Call the async functions first without awaiting, store the returned promises, then pass those promise references into Promise.all(): `Promise.all([fetchA(), fetchB()])`.
Assuming a rejected Promise.all() means all the underlying operations stopped, when in reality unrelated side effects (like a fetch completing) still occur in the background.
If operations need to actually stop on failure, use AbortController to cancel them explicitly — Promise.all() rejecting does not cancel anything on its own.
Real-World Examples
Loading Multiple Required Resources Before Rendering a Page
A page could only render meaningfully once the user profile, permissions, and feature flags had all loaded successfully — any one failing meant the page could not be shown at all.
const [profile, permissions, flags] = await Promise.all([
fetchProfile(),
fetchPermissions(),
fetchFeatureFlags(),
]);
// If any fails, show a full error state — partial data isn't usable here