All four run multiple Promises concurrently. All four return a single Promise. The difference โ the entire decision โ is how they handle failures.
The Quick Reference
| Method | Resolves when | Rejects when | Use case | |---|---|---|---| | Promise.all | ALL resolve | ANY rejects | All-or-nothing operations | | Promise.allSettled | ALL settle (either way) | Never | Batch operations with partial failure | | Promise.race | FIRST settles (resolve or reject) | FIRST settles with rejection | Timeouts, first-response wins | | Promise.any | FIRST resolves | ALL reject | Redundant sources, first success wins |
All four start all Promises simultaneously โ none waits for one to finish before starting the next. The difference is purely in how the returned Promise resolves.
Promise.all โ All or Nothing
Resolves when every Promise resolves. Rejects as soon as any single Promise rejects.
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments(),
])
// All three run simultaneously
// If any fails, the entire operation fails
The result is an array in input order โ regardless of which Promise resolved first:
const results = await Promise.all([
delay(300).then(() => 'slow'),
delay(100).then(() => 'fast'),
delay(200).then(() => 'medium'),
])
// ['slow', 'fast', 'medium'] โ always input order, not completion order
On rejection โ fail-fast:
try {
const results = await Promise.all([
fetchUser(1), // resolves in 200ms
fetchUser(2), // rejects in 100ms
fetchUser(3), // resolves in 150ms
])
} catch (err) {
// Runs after 100ms with fetchUser(2)'s rejection
// fetchUser(1) and fetchUser(3) are still running but their results are ignored
}
Promise.all is the right choice when all results are required for the operation to make sense. Fetching a product page that requires product data, seller data, and reviews โ if any one fails, the page can't render and you want to know immediately.
Promise.allSettled โ Wait for Everything, Fail Nothing
Resolves when every Promise settles โ fulfilled or rejected. Never rejects itself.
const results = await Promise.allSettled([
fetchUser(1),
fetchUser(2), // will reject
fetchUser(3),
])
results.forEach(result => { if (result.status === 'fulfilled') { console.log('Success:', result.value) } else { console.log('Failed:', result.reason) } })
Each result object has one of two shapes:
{ status: 'fulfilled', value: ... }โ the resolved value
{ status: 'rejected', reason: ... }โ the rejection reason
Real-world use โ dashboard with independent sections:
async function loadDashboard(userId) {
const [statsResult, activityResult, notificationsResult] = await Promise.allSettled([
fetchStats(userId),
fetchActivity(userId),
fetchNotifications(userId),
])
return { stats: statsResult.status === 'fulfilled' ? statsResult.value : null, activity: activityResult.status === 'fulfilled' ? activityResult.value : [], notifications: notificationsResult.status === 'fulfilled' ? notificationsResult.value : [], errors: [statsResult, activityResult, notificationsResult] .filter(r => r.status === 'rejected') .map(r => r.reason), } }
Three independent API calls. If notifications fail, the user still sees stats and activity. The failure is tracked but doesn't abort everything. Use allSettled whenever partial failure is acceptable.
Promise.race โ First Across the Line, Win or Lose
Resolves or rejects with the outcome of whichever Promise settles first โ whether that's a resolution or a rejection.
const result = await Promise.race([
fetchData(),
timeout(5000), // rejects after 5 seconds
])
// Resolves with fetchData()'s value if it completes in < 5 seconds
// Rejects with timeout error if fetchData() takes > 5 seconds
Implementing a timeout:
function withTimeout(promise, ms) {
const timer = new Promise((_, reject) =>
setTimeout(() => reject(new Error(Timed out after ${ms}ms)), ms)
)
return Promise.race([promise, timer])
}
try { const data = await withTimeout(fetchSlowAPI(), 3000) } catch (err) { if (err.message.startsWith('Timed out')) { console.error('Request took too long') } }
Finding the fastest server:
const fastest = await Promise.race([
fetchFromServer('us-east'),
fetchFromServer('eu-west'),
fetchFromServer('ap-south'),
])
// Use whichever responds first
The critical nuance: race doesn't cancel the other Promises when one wins. The others keep running โ their results are just ignored. If they have side effects, those still happen.
Promise.any โ First Success, Ignore Failures
Resolves with the first Promise that fulfills. Rejects only if ALL Promises reject.
const result = await Promise.any([
fetchFromCDN1(), // might fail
fetchFromCDN2(), // backup
fetchFromCDN3(), // secondary backup
])
// Returns as soon as any one CDN responds successfully
// Only fails if ALL three CDNs fail
When all reject, it throws an AggregateError containing all rejection reasons:
try {
const data = await Promise.any([
failingOperation1(),
failingOperation2(),
failingOperation3(),
])
} catch (err) {
err instanceof AggregateError // true
err.errors // [error1, error2, error3] โ all three rejection reasons
}
vs Promise.race: race settles on the first outcome regardless of success or failure. any waits for the first success, skipping failures. If you have redundant sources where any one succeeding is enough, any is the right tool.
// race: if the first one fails, you fail immediately
// any: if the first one fails, try the next, and the next...
const image = await Promise.any([ loadFromCache(), loadFromCDN(), loadFromOrigin(), ]) // Returns the first to succeed โ naturally degrades through backups
Comparison: Behavior on Mixed Results
Given: [resolves(A), rejects(E), resolves(B)]
| Method | Result | |---|---| | Promise.all | Rejects with E | | Promise.allSettled | Resolves with [{status:'fulfilled',value:A}, {status:'rejected',reason:E}, {status:'fulfilled',value:B}] | | Promise.race | Resolves or rejects with whichever settled first | | Promise.any | Resolves with A (first fulfillment) |
The Decision Tree
Do all results need to succeed for the operation to work?
Yes โ Promise.all
Can the operation succeed even if some inputs fail? Yes โ Promise.allSettled
Do you need the first result (success or failure)? Yes โ Promise.race (typically for timeouts)
Do you need the first successful result, ignoring failures? Yes โ Promise.any (redundant sources, fallback chains)
Sequential vs Parallel: The Mistake That Costs Performance
All four combinators run Promises in parallel โ they start all operations simultaneously. But there's one common mistake that accidentally makes them sequential:
// โ This creates Promises sequentially, then passes them to all:
// fetchUser() starts immediately when called, not when Promise.all runs
const p1 = await fetchUser(1) // starts AND awaits user 1
const p2 = await fetchUser(2) // starts AND awaits user 2 (after user 1 finishes)
// Total time: sum of both
// โ Create all Promises first, then combine: const p1 = fetchUser(1) // starts user 1 (no await) const p2 = fetchUser(2) // starts user 2 immediately await Promise.all([p1, p2]) // waits for both // Total time: max of both
// โ Or inline โ both start simultaneously: await Promise.all([fetchUser(1), fetchUser(2)])
The key is that calling an async function starts the operation. Awaiting it waits for the result. Pass un-awaited Promises to Promise.all, not awaited values.