JavaScript provides several Promise combinators for handling multiple async operations. Here's how to use them.
Promise.all#
1// Wait for all promises to resolve
2const promise1 = fetch('/api/users');
3const promise2 = fetch('/api/posts');
4const promise3 = fetch('/api/comments');
5
6Promise.all([promise1, promise2, promise3])
7 .then(([users, posts, comments]) => {
8 console.log('All data loaded');
9 })
10 .catch(error => {
11 // If ANY promise rejects, catch fires
12 console.error('One request failed:', error);
13 });
14
15// With async/await
16async function loadAllData() {
17 try {
18 const [users, posts, comments] = await Promise.all([
19 fetch('/api/users').then(r => r.json()),
20 fetch('/api/posts').then(r => r.json()),
21 fetch('/api/comments').then(r => r.json()),
22 ]);
23
24 return { users, posts, comments };
25 } catch (error) {
26 console.error('Failed to load data:', error);
27 throw error;
28 }
29}
30
31// Parallel processing
32async function processItems(items) {
33 const results = await Promise.all(
34 items.map(async item => {
35 const processed = await processItem(item);
36 return processed;
37 })
38 );
39
40 return results;
41}
42
43// With timeout
44function withTimeout(promise, ms) {
45 const timeout = new Promise((_, reject) =>
46 setTimeout(() => reject(new Error('Timeout')), ms)
47 );
48 return Promise.race([promise, timeout]);
49}
50
51const results = await withTimeout(
52 Promise.all([fetch('/api/a'), fetch('/api/b')]),
53 5000
54);Promise.allSettled#
1// Wait for all promises, regardless of outcome
2const promises = [
3 fetch('/api/users'),
4 fetch('/api/broken-endpoint'), // This will fail
5 fetch('/api/posts'),
6];
7
8Promise.allSettled(promises)
9 .then(results => {
10 results.forEach((result, index) => {
11 if (result.status === 'fulfilled') {
12 console.log(`Promise ${index} succeeded:`, result.value);
13 } else {
14 console.log(`Promise ${index} failed:`, result.reason);
15 }
16 });
17 });
18
19// Separate successes and failures
20async function fetchAllWithStatus(urls) {
21 const results = await Promise.allSettled(
22 urls.map(url => fetch(url).then(r => r.json()))
23 );
24
25 const successes = results
26 .filter(r => r.status === 'fulfilled')
27 .map(r => r.value);
28
29 const failures = results
30 .filter(r => r.status === 'rejected')
31 .map(r => r.reason);
32
33 return { successes, failures };
34}
35
36// Retry failed requests
37async function fetchWithRetry(urls, maxRetries = 3) {
38 let remaining = urls;
39
40 for (let attempt = 0; attempt < maxRetries; attempt++) {
41 const results = await Promise.allSettled(
42 remaining.map(url => fetch(url).then(r => r.json()))
43 );
44
45 const succeeded = [];
46 const failed = [];
47
48 results.forEach((result, index) => {
49 if (result.status === 'fulfilled') {
50 succeeded.push(result.value);
51 } else {
52 failed.push(remaining[index]);
53 }
54 });
55
56 if (failed.length === 0) {
57 return succeeded;
58 }
59
60 remaining = failed;
61 }
62
63 throw new Error(`Failed after ${maxRetries} retries`);
64}Promise.race#
1// First promise to settle wins
2const fast = new Promise(resolve =>
3 setTimeout(() => resolve('fast'), 100)
4);
5
6const slow = new Promise(resolve =>
7 setTimeout(() => resolve('slow'), 500)
8);
9
10Promise.race([fast, slow])
11 .then(result => console.log(result)); // 'fast'
12
13// Timeout pattern
14function fetchWithTimeout(url, timeout) {
15 const fetchPromise = fetch(url);
16 const timeoutPromise = new Promise((_, reject) =>
17 setTimeout(() => reject(new Error('Request timeout')), timeout)
18 );
19
20 return Promise.race([fetchPromise, timeoutPromise]);
21}
22
23try {
24 const response = await fetchWithTimeout('/api/data', 5000);
25 const data = await response.json();
26} catch (error) {
27 if (error.message === 'Request timeout') {
28 console.log('Request took too long');
29 }
30}
31
32// First successful response from multiple sources
33async function fetchFromMirrors(mirrors) {
34 return Promise.race(
35 mirrors.map(url =>
36 fetch(url).then(response => {
37 if (!response.ok) throw new Error('Failed');
38 return response;
39 })
40 )
41 );
42}
43
44// Cancel on first result
45function raceWithCancellation(promises, signal) {
46 return Promise.race([
47 ...promises,
48 new Promise((_, reject) => {
49 signal.addEventListener('abort', () => {
50 reject(new DOMException('Aborted', 'AbortError'));
51 });
52 }),
53 ]);
54}Promise.any#
1// First promise to FULFILL wins (ignores rejections)
2const promises = [
3 Promise.reject('Error 1'),
4 new Promise(resolve => setTimeout(() => resolve('Success'), 100)),
5 Promise.reject('Error 2'),
6];
7
8Promise.any(promises)
9 .then(result => console.log(result)) // 'Success'
10 .catch(error => {
11 // AggregateError if ALL promises reject
12 console.log(error.errors); // All rejection reasons
13 });
14
15// Fallback pattern
16async function fetchWithFallbacks(urls) {
17 try {
18 const response = await Promise.any(
19 urls.map(url => fetch(url).then(r => {
20 if (!r.ok) throw new Error(`HTTP ${r.status}`);
21 return r;
22 }))
23 );
24 return response.json();
25 } catch (error) {
26 if (error instanceof AggregateError) {
27 console.log('All sources failed:', error.errors);
28 }
29 throw error;
30 }
31}
32
33// First available service
34async function connectToService(endpoints) {
35 try {
36 const connection = await Promise.any(
37 endpoints.map(async endpoint => {
38 const health = await fetch(`${endpoint}/health`);
39 if (!health.ok) throw new Error('Unhealthy');
40 return { endpoint, status: 'connected' };
41 })
42 );
43 return connection;
44 } catch (error) {
45 throw new Error('No healthy services available');
46 }
47}Promise.resolve and Promise.reject#
1// Create resolved promise
2const resolved = Promise.resolve(42);
3resolved.then(value => console.log(value)); // 42
4
5// Wrap sync value in promise
6function maybeAsync(value) {
7 return Promise.resolve(value);
8}
9
10// Create rejected promise
11const rejected = Promise.reject(new Error('Failed'));
12rejected.catch(error => console.log(error.message)); // 'Failed'
13
14// Normalize to promise
15async function getData(source) {
16 // Handle both sync and async sources
17 const data = await Promise.resolve(source);
18 return process(data);
19}
20
21// Thenable conversion
22const thenable = {
23 then(resolve) {
24 resolve('from thenable');
25 }
26};
27
28Promise.resolve(thenable)
29 .then(value => console.log(value)); // 'from thenable'Practical Patterns#
1// Batch processing with concurrency limit
2async function batchProcess(items, fn, concurrency = 5) {
3 const results = [];
4
5 for (let i = 0; i < items.length; i += concurrency) {
6 const batch = items.slice(i, i + concurrency);
7 const batchResults = await Promise.all(batch.map(fn));
8 results.push(...batchResults);
9 }
10
11 return results;
12}
13
14// Sequential execution
15async function sequential(tasks) {
16 const results = [];
17
18 for (const task of tasks) {
19 results.push(await task());
20 }
21
22 return results;
23}
24
25// Or with reduce
26function sequentialReduce(tasks) {
27 return tasks.reduce(
28 (promise, task) => promise.then(results =>
29 task().then(result => [...results, result])
30 ),
31 Promise.resolve([])
32 );
33}
34
35// Retry with exponential backoff
36async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) {
37 let lastError;
38
39 for (let i = 0; i < maxRetries; i++) {
40 try {
41 return await fn();
42 } catch (error) {
43 lastError = error;
44 const delay = baseDelay * Math.pow(2, i);
45 await new Promise(r => setTimeout(r, delay));
46 }
47 }
48
49 throw lastError;
50}
51
52// Poll until condition
53async function pollUntil(fn, condition, interval = 1000, timeout = 30000) {
54 const start = Date.now();
55
56 while (Date.now() - start < timeout) {
57 const result = await fn();
58 if (condition(result)) {
59 return result;
60 }
61 await new Promise(r => setTimeout(r, interval));
62 }
63
64 throw new Error('Polling timeout');
65}
66
67// Usage
68const status = await pollUntil(
69 () => fetch('/api/job/123').then(r => r.json()),
70 result => result.status === 'complete',
71 2000,
72 60000
73);Error Handling#
1// Catch specific errors
2class NetworkError extends Error {}
3class ValidationError extends Error {}
4
5async function fetchData(url) {
6 try {
7 const response = await fetch(url);
8
9 if (!response.ok) {
10 throw new NetworkError(`HTTP ${response.status}`);
11 }
12
13 const data = await response.json();
14
15 if (!isValid(data)) {
16 throw new ValidationError('Invalid data');
17 }
18
19 return data;
20 } catch (error) {
21 if (error instanceof NetworkError) {
22 // Handle network errors
23 } else if (error instanceof ValidationError) {
24 // Handle validation errors
25 } else {
26 // Unknown error
27 throw error;
28 }
29 }
30}
31
32// Finally for cleanup
33async function withCleanup() {
34 const resource = await acquire();
35
36 try {
37 return await useResource(resource);
38 } finally {
39 await release(resource);
40 }
41}
42
43// Promise.prototype.finally
44fetch('/api/data')
45 .then(response => response.json())
46 .catch(error => console.error(error))
47 .finally(() => {
48 hideLoadingSpinner();
49 });Best Practices#
Choosing Methods:
✓ Promise.all - all must succeed
✓ Promise.allSettled - need all results
✓ Promise.race - first to settle
✓ Promise.any - first to succeed
Error Handling:
✓ Always handle rejections
✓ Use specific error types
✓ Add timeouts to prevent hanging
✓ Log errors appropriately
Performance:
✓ Parallelize independent operations
✓ Limit concurrency for resources
✓ Batch large operations
✓ Cancel unnecessary requests
Avoid:
✗ Unhandled promise rejections
✗ Infinite parallel requests
✗ Forgetting await
✗ Nested promise chains
Conclusion#
Promise combinators enable powerful async patterns. Use Promise.all when all operations must succeed, Promise.allSettled when you need all results regardless of outcome, Promise.race for timeouts and first-response patterns, and Promise.any for fallback strategies. Combine these with proper error handling for robust async code.