Problem Statement
Compare Promises and async/await. Explain error handling, parallel execution, and best practices for each.
Explanation
Promises and async await are two ways to handle asynchronous operations in JavaScript. Async await is built on Promises and provides cleaner syntax.
Promises:
Represent eventual completion or failure of async operation. Use then for success, catch for errors, finally for cleanup. Can chain multiple operations with then. Promise.all for parallel execution. Promise.race for first completed. More explicit about async nature.
Async Await:
Syntactic sugar over Promises. Makes async code look synchronous. Use try catch for error handling. Easier to read and debug. Can use await in loops naturally. Top-level await in modules.
Error handling:
Promises use catch method or second argument to then. Async await uses try catch blocks. Catch can handle errors from multiple thens. Try catch can wrap multiple awaits. Finally block works for cleanup in both.
Parallel execution:
Promises naturally support Promise.all for concurrent operations. Async await needs Promise.all explicitly. Sequential awaits are slower. Always use Promise.all for independent operations. Promise.allSettled waits for all regardless of failures.
When to use Promises:
Complex chaining with transformations. Multiple independent operations. When you need promise objects. Working with Promise combinators. Callback-based APIs with promisify.
When to use async await:
Linear flow of operations. Better readability needed. Using with loops. Complex try catch needed. Most new code.
Best practices:
Always handle errors with catch or try catch. Use Promise.all for parallel operations. Avoid sequential awaits for independent operations. Return promises from async functions when appropriate. Use Promise.allSettled when all results needed. Name async functions descriptively.
Common mistakes:
Forgetting to await or return promises. Sequential awaits causing slow code. Not handling promise rejections. Mixing callbacks with promises. Using async without await unnecessarily.
Understanding both approaches and when to use each shows mastery of asynchronous JavaScript.
Code Solution
SolutionRead Only
// PROMISES
// Basic promise
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({id, name: 'John'});
} else {
reject('Invalid ID');
}
}, 1000);
});
}
// Using promises
fetchUser(1)
.then(user => {
console.log('User:', user);
return fetchPosts(user.id);
})
.then(posts => {
console.log('Posts:', posts);
return fetchComments(posts[0].id);
})
.then(comments => {
console.log('Comments:', comments);
})
.catch(error => {
console.error('Error:', error);
})
.finally(() => {
console.log('Cleanup');
});
// ASYNC/AWAIT
async function getUserData(id) {
try {
const user = await fetchUser(id);
console.log('User:', user);
const posts = await fetchPosts(user.id);
console.log('Posts:', posts);
const comments = await fetchComments(posts[0].id);
console.log('Comments:', comments);
return {user, posts, comments};
} catch (error) {
console.error('Error:', error);
throw error; // Re-throw if needed
} finally {
console.log('Cleanup');
}
}
// PARALLEL EXECUTION
// Promises - Promise.all
Promise.all([
fetchUser(1),
fetchPosts(1),
fetchComments(1)
])
.then(([user, posts, comments]) => {
console.log({user, posts, comments});
})
.catch(error => {
console.error('One failed:', error);
});
// Async/await - sequential (SLOW)
async function sequentialSlow() {
const user = await fetchUser(1); // Wait
const posts = await fetchPosts(1); // Then wait
const comments = await fetchComments(1); // Then wait
return {user, posts, comments};
}
// Total time: sum of all
// Async/await - parallel (FAST)
async function parallelFast() {
const [user, posts, comments] = await Promise.all([
fetchUser(1),
fetchPosts(1),
fetchComments(1)
]);
return {user, posts, comments};
}
// Total time: max of all
// Start promises, then await
async function parallelPattern() {
const userPromise = fetchUser(1);
const postsPromise = fetchPosts(1);
const commentsPromise = fetchComments(1);
const user = await userPromise;
const posts = await postsPromise;
const comments = await commentsPromise;
return {user, posts, comments};
}
// ERROR HANDLING COMPARISON
// Promises - catch chain
fetchUser(1)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.catch(error => {
// Catches any error in the chain
console.error(error);
});
// Async/await - try/catch
async function withErrorHandling() {
try {
const user = await fetchUser(1);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
} catch (error) {
// Catches any error
console.error(error);
}
}
// Multiple try/catch for specific handling
async function specificErrorHandling() {
let user, posts;
try {
user = await fetchUser(1);
} catch (error) {
console.error('User fetch failed:', error);
return null;
}
try {
posts = await fetchPosts(user.id);
} catch (error) {
console.error('Posts fetch failed:', error);
posts = [];
}
return {user, posts};
}
// PROMISE COMBINATORS
// Promise.all - all must succeed
Promise.all([promise1, promise2, promise3])
.then(results => console.log(results))
.catch(err => console.log('One failed'));
// Promise.allSettled - wait for all
Promise.allSettled([promise1, promise2, promise3])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.log('Failed:', result.reason);
}
});
});
// Promise.race - first to finish
Promise.race([promise1, promise2])
.then(result => console.log('First:', result));
// Promise.any - first fulfilled
Promise.any([promise1, promise2, promise3])
.then(result => console.log('First success:', result));
// PRACTICAL PATTERNS
// Retry logic with promises
function retry(fn, retries = 3) {
return fn().catch(err => {
if (retries > 0) {
return retry(fn, retries - 1);
}
throw err;
});
}
// Retry logic with async/await
async function retryAsync(fn, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) throw error;
}
}
}
// Timeout wrapper
function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject('Timeout'), ms)
)
]);
}
// Sequential processing
async function processSequentially(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
// Parallel processing
async function processParallel(items) {
return Promise.all(
items.map(item => processItem(item))
);
}
// BEST PRACTICES
// Always return promises
async function good() {
return await fetchData(); // or just: return fetchData();
}
// Avoid unnecessary async
function unnecessary() { // Don't need async here
return fetchData(); // Already returns promise
}
// Handle errors
async function alwaysHandle() {
try {
return await fetchData();
} catch (error) {
console.error(error);
return null; // Provide fallback
}
}
// Use Promise.all for independent operations
async function efficient() {
return Promise.all([
fetchUser(),
fetchPosts(),
fetchSettings()
]);
}