JavaScript Async Patterns - Promises, Async/Await, and Advanced Techniques
Mahesh Waghmare Asynchronous programming is fundamental to modern JavaScript. This guide covers Promises, async/await, error handling, and advanced patterns for managing asynchronous operations.
Introduction to Async JavaScript
JavaScript is single-threaded but handles asynchronous operations through callbacks, Promises, and async/await.
Why Async?
- Non-blocking operations
- Better user experience
- Efficient resource usage
- Handling I/O operations
Evolution:
- Callbacks → Promises → Async/Await
Understanding Promises
Creating Promises
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Success!');
}, 1000);
});
Promise Methods
then/catch:
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
Promise.all:
const promises = [
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments'),
];
Promise.all(promises)
.then(responses => Promise.all(responses.map(r => r.json())))
.then(([users, posts, comments]) => {
console.log({ users, posts, comments });
});
Promise.allSettled:
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index} succeeded`);
} else {
console.log(`Promise ${index} failed`);
}
});
});
Async/Await Syntax
Basic Async Function
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
return data;
}
Sequential vs Concurrent
Sequential:
async function sequential() {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
return { user, posts, comments };
}
Concurrent:
async function concurrent() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments(),
]);
return { user, posts, comments };
}
Error Handling
Try/Catch
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
Error Boundaries
async function safeFetch(url) {
try {
const response = await fetch(url);
return { data: await response.json(), error: null };
} catch (error) {
return { data: null, error: error.message };
}
}
Concurrent Operations
Parallel Execution
async function processItems(items) {
const results = await Promise.all(
items.map(item => processItem(item))
);
return results;
}
Limiting Concurrency
async function processWithLimit(items, limit) {
const results = [];
for (let i = 0; i < items.length; i += limit) {
const batch = items.slice(i, i + limit);
const batchResults = await Promise.all(
batch.map(item => processItem(item))
);
results.push(...batchResults);
}
return results;
}
Advanced Patterns
Retry Pattern
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
Timeout Pattern
function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
),
]);
}
Conclusion
JavaScript async patterns enable:
- Non-blocking operations
- Better error handling
- Concurrent execution
- Cleaner code with async/await
Key principles:
- Use async/await for readability
- Handle errors properly
- Use Promise.all for concurrent operations
- Implement retry and timeout patterns
Mastering async patterns is essential for modern JavaScript development.
Written by Mahesh Waghmare
I bridge the gap between WordPress architecture and modern React frontends. Currently building tools for the AI era.
Follow on Twitter →