Advanced Async Patterns
Promise.all()
Execute multiple promises in parallel and wait for all to complete.
Promise.all basic usage
const fetchUserData = (id) => {
return new Promise(resolve => {
setTimeout(() => resolve({ id, name: `User ${id}` }), 100);
});
};
const fetchUserPosts = (id) => {
return new Promise(resolve => {
setTimeout(() => resolve({ userId: id, posts: 3 }), 150);
});
};
// Fetch data for user 1
Promise.all([
fetchUserData(1),
fetchUserPosts(1)
]).then(([userData, postData]) => {
console.log('User:', userData);
console.log('Posts:', postData);
});
// Takes ~150ms (the longest promise)
Real-world example:
Promise.all with API calls
async function loadUserDashboard(userId) {
try {
const [user, posts, comments, notifications] = await Promise.all([
fetchUser(userId),
fetchPosts(userId),
fetchComments(userId),
fetchNotifications(userId)
]);
return {
user,
posts,
comments,
notifications
};
} catch (error) {
console.error('Failed to load dashboard:', error);
throw error;
}
}
Error handling:
Promise.all error handling
Promise.all([
fetchUser(1),
fetchPosts(1),
fetchInvalidData() // This will reject
]).catch(error => {
// If ANY promise rejects, the whole thing fails
console.error('One or more operations failed:', error);
});
// Note: If one fails, others continue running but result is rejected
Promise.race()
Complete when the first promise settles (resolves or rejects).
Promise.race basic usage
const fetchWithTimeout = (url, timeout = 5000) => {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Request timeout')), timeout);
});
return Promise.race([
fetch(url),
timeoutPromise
]);
};
// Usage
fetchWithTimeout('https://api.example.com/data', 3000)
.then(response => response.json())
.catch(error => console.error('Request failed:', error.message));
Practical example:
Promise.race for fallback APIs
const fetchFromMultipleSources = async () => {
try {
return await Promise.race([
fetch('https://primary-api.com/data'),
fetch('https://backup-api-1.com/data'),
fetch('https://backup-api-2.com/data')
]);
} catch (error) {
console.error('All sources failed');
}
};
Promise.allSettled()
Wait for all promises to settle (resolve OR reject) and get all results.
Promise.allSettled basic usage
const promises = [
Promise.resolve('Success 1'),
Promise.reject('Error 1'),
Promise.resolve('Success 2'),
Promise.reject('Error 2')
];
Promise.allSettled(promises).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('✓ Success:', result.value);
} else {
console.log('✗ Failed:', result.reason);
}
});
});
Real-world example:
allSettled for batch operations
async function sendNotificationsToUsers(userIds) {
const promises = userIds.map(id =>
sendNotification(id).catch(error => ({
userId: id,
error: error.message
}))
);
const results = await Promise.allSettled(promises);
const successful = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
console.log(`Sent ${successful.length} notifications, ${failed.length} failed`);
return {
successful: successful.map(r => r.value),
failed: failed.map(r => r.reason)
};
}
Promise.any()
Complete when the first promise resolves (ignores rejections).
Promise.any basic usage
Promise.any([
Promise.reject('Error 1'),
Promise.reject('Error 2'),
Promise.resolve('Success!')
]).then(result => {
console.log(result); // 'Success!'
}).catch(error => {
// AggregateError if ALL reject
console.log('All promises rejected');
});
Concurrent Operations with Limits
Control concurrency when dealing with many operations:
Concurrency limiter
class ConcurrencyLimiter {
constructor(limit) {
this.limit = limit;
this.running = 0;
this.queue = [];
}
async run(fn) {
while (this.running >= this.limit) {
await new Promise(resolve => this.queue.push(resolve));
}
this.running++;
try {
return await fn();
} finally {
this.running--;
const resolve = this.queue.shift();
if (resolve) resolve();
}
}
}
// Usage
const limiter = new ConcurrencyLimiter(3); // Max 3 concurrent
const tasks = Array.from({ length: 10 }, (_, i) =>
limiter.run(() => fetchData(i))
);
await Promise.all(tasks);
Async Generator Functions
Combine async and generator patterns:
Async generator
async function* fetchDataInBatches(items, batchSize = 5) {
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const results = await Promise.all(
batch.map(item => fetchData(item))
);
yield results;
}
}
// Usage
(async () => {
for await (const batch of fetchDataInBatches(itemIds, 10)) {
console.log('Processing batch:', batch);
}
})();
Retry Logic
Automatically retry failed operations:
Retry with exponential backoff
async function retryWithBackoff(fn, maxRetries = 3, initialDelay = 100) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries) throw error;
const delay = initialDelay * Math.pow(2, attempt - 1);
console.log(`Attempt ${attempt} failed. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Usage
const data = await retryWithBackoff(
() => fetch('https://api.example.com/data').then(r => r.json()),
3,
100
);
Debouncing and Throttling Async Functions
Async debounce
function debounceAsync(fn, delay) {
let timeoutId;
let lastPromise = null;
return async function debounced(...args) {
clearTimeout(timeoutId);
return new Promise((resolve, reject) => {
timeoutId = setTimeout(async () => {
try {
const result = await fn(...args);
resolve(result);
} catch (error) {
reject(error);
}
}, delay);
});
};
}
// Usage: Debounced API search
const debouncedSearch = debounceAsync(
async (query) => {
const response = await fetch(`/api/search?q=${query}`);
return response.json();
},
300
);
input.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
Abort Controller for Cancellation
Cancel ongoing operations:
AbortController for fetch
const controller = new AbortController();
// Start a long request
const fetchPromise = fetch('https://slow-api.example.com/data', {
signal: controller.signal
});
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);
try {
const response = await fetchPromise;
const data = await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was cancelled');
}
}
Promise Chain vs Async/Await
Understanding the differences:
Promise chain approach
function loadUserWithPosts(userId) {
return fetchUser(userId)
.then(user =>
fetchPosts(user.id).then(posts => ({
...user,
posts
}))
)
.catch(error => console.error('Error:', error));
}
Async/await approach (cleaner)
async function loadUserWithPosts(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
return {
...user,
posts
};
} catch (error) {
console.error('Error:', error);
}
}
Practical Real-World Example
Complete data loading workflow
async function loadDashboardData(userId) {
const startTime = Date.now();
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000); // 30s timeout
try {
// Load critical data in parallel
const [user, stats] = await Promise.all([
retryWithBackoff(() => fetchUser(userId)),
retryWithBackoff(() => fetchStats(userId))
]);
// Load additional data
const [posts, comments, followers] = await Promise.allSettled([
fetchPosts(userId, { signal: controller.signal }),
fetchComments(userId, { signal: controller.signal }),
fetchFollowers(userId, { signal: controller.signal })
]);
// Process results
const dashboard = {
user,
stats,
posts: posts.status === 'fulfilled' ? posts.value : null,
comments: comments.status === 'fulfilled' ? comments.value : null,
followers: followers.status === 'fulfilled' ? followers.value : null
};
console.log(`Dashboard loaded in ${Date.now() - startTime}ms`);
return dashboard;
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Dashboard load timeout');
}
throw error;
} finally {
clearTimeout(timeout);
}
}
Performance Comparison
| Method | Use Case | Performance |
|---|---|---|
Promise.all() | All succeed or fail together | Fastest |
Promise.race() | First to finish matters | Fast |
Promise.allSettled() | Need all results regardless | Medium |
Promise.any() | One success is enough | Medium |
| Sequential awaits | Depend on previous results | Slowest |
Key Takeaways
- Promise.all(): Wait for all promises, fail if any reject
- Promise.race(): Use first to finish (great for timeouts)
- Promise.allSettled(): Get all results, even failures
- Promise.any(): Use first success, ignore rejections
- AbortController: Cancel long-running operations
- Retry logic: Handle transient failures gracefully
- Async generators: Stream large datasets efficiently