You've got multiple async operations to run in parallel. Do you reach for Promise.all, Promise.allSettled, Promise.race, or Promise.any?
They all do similar things — run Promises in parallel — but they behave differently when things go wrong. Pick the wrong one, and you'll silently swallow errors or throw away perfectly good data.
Here's a practical guide to choosing the right Promise combinator.
Quick Comparison
| Method | All must succeed? | Fails fast? | Reject value | Fulfills with |
|---|---|---|---|---|
Promise.all | Yes | Yes | First rejection | Array of results |
Promise.allSettled | No | No | Never rejects | Array of {status, value/reason} |
Promise.race | No | Yes | First rejection OR fulfillment | First settled value |
Promise.any | At least one | No | AggregateError | First fulfillment |
Let's break down each one with real examples.
Promise.all: All or Nothing
Use Promise.all when you need all operations to succeed. If one fails, the entire batch fails.
const fetchUser = () => fetch('/api/user').then(r => r.json());
const fetchPosts = () => fetch('/api/posts').then(r => r.json());
const fetchSettings = () => fetch('/api/settings').then(r => r.json());
// All three must succeed
Promise.all([fetchUser(), fetchPosts(), fetchSettings()])
.then(([user, posts, settings]) => {
// user = user data
// posts = posts array
// settings = settings object
renderDashboard(user, posts, settings);
})
.catch(err => {
// If ANY fetch fails, you land here
showErrorMessage('Failed to load dashboard');
});When to use it:
- Loading dependent data (dashboard needs user + posts + settings)
- Batch operations where partial success is useless
- Parallel API calls that all must complete
Gotcha: Promise.all fails fast. The first rejection stops everything, even if other Promises would have succeeded.
// Promise 1 resolves after 2s
// Promise 2 rejects after 1s
// Promise 3 resolves after 3s
Promise.all([
delay(2000).then(() => 'one'),
delay(1000).then(() => Promise.reject(new Error('two failed'))),
delay(3000).then(() => 'three')
])
.catch(err => {
console.log(err.message); // "two failed"
// "one" and "three" are discarded
});Promise.allSettled: Don't Fail Fast
Use Promise.allSettled when you want to run all Promises and handle each result individually. It never rejects — you always get an array of settled statuses.
const urls = ['/api/a', '/api/b', '/api/c', '/api/d'];
Promise.allSettled(urls.map(url => fetch(url)))
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`${urls[index]}:`, result.value);
} else {
console.error(`${urls[index]} failed:`, result.reason);
}
});
});When to use it:
- Batch operations where partial success is okay
- Running independent tasks and aggregating results
- Logging/reporting where you want to see all outcomes
Gotcha: You get objects, not raw values:
const results = await Promise.allSettled([
Promise.resolve('success'),
Promise.reject(new Error('failed'))
]);
// results[0] = { status: 'fulfilled', value: 'success' }
// results[1] = { status: 'rejected', reason: Error('failed') }Promise.race: First to Settle Wins
Use Promise.race when you only care about the first result, whether it's a success or failure. It's a race — the first Promise to settle wins.
// Fetch with timeout
const fetchWithTimeout = (url, timeout = 5000) => {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout)
)
]);
};
fetchWithTimeout('/api/slow-endpoint', 3000)
.then(response => console.log('Got response:', response))
.catch(err => console.error('Failed:', err.message));When to use it:
- Implementing timeouts
- Caching (race between cache and network)
- Trying multiple sources and taking the first response
Gotcha: The first Promise to settle wins, not the first to fulfill. A rejection wins over a slower fulfillment:
Promise.race([
delay(1000).then(() => 'slow success'),
delay(500).then(() => Promise.reject(new Error('fast failure')))
])
.catch(err => {
console.log(err.message); // "fast failure" — even though it's a rejection
});Promise.any: First Success Wins
Use Promise.any when you want the first successful result and you're okay with some failures. It rejects only if all Promises fail.
// Try multiple servers, use the first that responds
const servers = [
'https://api1.example.com/data',
'https://api2.example.com/data',
'https://api3.example.com/data'
];
Promise.any(servers.map(url => fetch(url)))
.then(response => response.json())
.then(data => console.log('Got data from:', data))
.catch(err => {
if (err instanceof AggregateError) {
console.error('All servers failed:', err.errors);
}
});When to use it:
- Fallback strategies (try multiple sources)
- Fastest available CDN endpoint
- Any-success-is-enough scenarios
Gotcha: If all Promises reject, you get an AggregateError containing all rejection reasons:
Promise.any([
Promise.reject(new Error('Server 1 failed')),
Promise.reject(new Error('Server 2 failed')),
Promise.reject(new Error('Server 3 failed'))
])
.catch(err => {
console.log(err instanceof AggregateError); // true
console.log(err.errors); // [Error('Server 1 failed'), Error('Server 2 failed'), Error('Server 3 failed')]
});Real-World Example: Image Gallery with Fallbacks
Here's a practical example combining multiple combinators:
async function loadGalleryWithFallbacks(imageUrls, timeout = 3000) {
// 1. Try to fetch all images with timeout
const fetchWithTimeout = (url) => Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout)
)
]);
// 2. Get results for all images (don't fail fast)
const results = await Promise.allSettled(
imageUrls.map(url => fetchWithTimeout(url))
);
// 3. Separate successes and failures
const successful = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
const failed = results
.filter(r => r.status === 'rejected')
.map(r => r.reason);
// 4. If we have some success, use them
if (successful.length > 0) {
console.log(`Loaded ${successful.length}/${imageUrls.length} images`);
return successful;
}
// 5. If all failed, try fallback URLs (first success wins)
const fallbackUrls = imageUrls.map(url => url.replace('/images/', '/fallback/'));
try {
const fallbackResult = await Promise.any(
fallbackUrls.map(url => fetch(url))
);
console.log('Using fallback image');
return [fallbackResult];
} catch (err) {
// Even fallbacks failed
console.error('All images and fallbacks failed:', err.errors);
return [];
}
}Which One Should You Use?
| Scenario | Use |
|---|---|
| Need all results to succeed | Promise.all |
| Want all results, even if some fail | Promise.allSettled |
| Only need first result (success or fail) | Promise.race |
| Need first success, failures okay | Promise.any |
| Implementing a timeout | Promise.race |
| Fallback strategies | Promise.any |
| Batch independent operations | Promise.allSettled |
Common Mistakes
Mistake 1: Using Promise.all when you don't need all results
// Bad: Loading 10 avatars, but you're okay with some failing
Promise.all(avatars.map(loadAvatar))
.then(allAvatars => displayAvatars(allAvatars))
.catch(err => {
// One failed avatar = no avatars at all
displayError();
});
// Good: Use allSettled to show what you can
Promise.allSettled(avatars.map(loadAvatar))
.then(results => {
const loaded = results.filter(r => r.status === 'fulfilled').map(r => r.value);
displayAvatars(loaded);
});Mistake 2: Forgetting that Promise.race rejects on first failure
// Bad: Expecting race to give you the first success
Promise.race([
fetch('/api/fast-but-flaky'),
fetch('/api/slow-but-reliable')
])
.then(res => console.log('Got response'))
.catch(err => {
// If the fast-but-flaky fails first, you never try the slow-but-reliable
});
// Good: Use Promise.any for first-success semantics
Promise.any([
fetch('/api/fast-but-flaky'),
fetch('/api/slow-but-reliable')
])
.then(res => console.log('Got response'));Mistake 3: Not handling empty arrays
// Bad: Promise.all with empty array resolves immediately
Promise.all([]).then(results => {
console.log(results); // [] — might not be what you expect
});
// Be explicit about empty cases
const promises = urls.map(fetch);
if (promises.length === 0) {
return [];
}
return Promise.all(promises);Summary
- Promise.all: All must succeed, fails fast. Use when you need everything.
- Promise.allSettled: Runs all, never rejects. Use for batch operations where partial success is okay.
- Promise.race: First to settle wins. Use for timeouts and first-response scenarios.
- Promise.any: First success wins. Use for fallbacks and fastest-available scenarios.
Pick the right tool for the job, and you'll write more resilient async code.