Asynchronous Programming Patterns
JavaScript offers various patterns for handling asynchronous operations. Understanding these patterns is crucial for writing efficient and maintainable async code.
Callbacks
// Basic callback pattern
function fetchData(callback) {
setTimeout(() => {
callback(null, { data: 'Success' });
}, 1000);
}
// Callback hell example
fetchUser(function(err, user) {
if (err) handleError(err);
fetchProfile(user.id, function(err, profile) {
if (err) handleError(err);
fetchPosts(profile.id, function(err, posts) {
if (err) handleError(err);
// Deep nesting continues...
});
});
});Promises
// Creating promises
const promise = new Promise((resolve, reject) => {
if (success) {
resolve('Success!');
} else {
reject(new Error('Failed'));
}
});
// Promise chaining
fetchUser(userId)
.then(user => fetchProfile(user.id))
.then(profile => fetchPosts(profile.id))
.catch(error => handleError(error));
// Promise.all for parallel execution
Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
])
.then(([users, posts, comments]) => {
// Handle all responses
});
// Promise.race for timeouts
Promise.race([
fetch('/api/data'),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 5000)
)
]);Async/Await
// Basic async/await
async function fetchUserData() {
try {
const user = await fetchUser(userId);
const profile = await fetchProfile(user.id);
const posts = await fetchPosts(profile.id);
return { user, profile, posts };
} catch (error) {
handleError(error);
}
}
// Parallel execution with async/await
async function fetchAllData() {
const [users, posts, comments] = await Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
]);
return { users, posts, comments };
}
// Error handling patterns
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fetch(url);
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
}
}
}Generators and Async Iterators
// Generator function
async function* createAsyncIterator() {
for (let i = 0; i < 5; i++) {
await new Promise(r => setTimeout(r, 1000));
yield i;
}
}
// Using async iterator
async function processItems() {
for await (const item of createAsyncIterator()) {
console.log(item);
}
}
// Custom async iterator
const asyncIterable = {
async *[Symbol.asyncIterator]() {
yield 'Hello';
yield 'Async';
yield 'World';
}
};Common Interview Follow-up Questions
- How do you handle errors in async/await vs promises?
- What are the benefits of using async/await over promises?
- How do you implement timeout for async operations?
- When would you use generators over regular async functions?
Best Practices
- Always handle errors in async operations
- Use async/await for better readability
- Implement proper timeout mechanisms
- Consider parallel execution when possible
- Use appropriate error retry strategies