Async/Await vs Promises vs Callbacks: JavaScript Async Patterns Explained
JavaScript has three async patterns. Understanding when each applies prevents bugs and makes code readable.
Callbacks: The Origin
// Node.js-style callback (error-first)
fs.readFile('/path/to/file', 'utf8', (err, data) => {
if (err) {
console.error('Error:', err);
return;
}
console.log(data);
});
// Callback hell — the reason Promises were invented
getUser(userId, (err, user) => {
getOrders(user.id, (err, orders) => {
getProduct(orders[0].productId, (err, product) => {
// Deeply nested, error handling everywhere
});
});
});
Promises: Chainable Async
// Promise chain — flatter than callbacks
getUser(userId)
.then(user => getOrders(user.id))
.then(orders => getProduct(orders[0].productId))
.then(product => console.log(product))
.catch(err => console.error(err));
// Creating a Promise
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Promisifying callbacks
const readFile = (path) => new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => err ? reject(err) : resolve(data));
});
// Or use util.promisify(fs.readFile)
Async/Await: The Modern Standard
// Same logic, reads like synchronous code
async function processOrder(userId) {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const product = await getProduct(orders[0].productId);
return product;
} catch (err) {
console.error('Failed:', err);
throw err;
}
}
Parallel vs Sequential
// Sequential: each awaits the previous — slow
const user = await getUser(userId); // 100ms
const orders = await getOrders(userId); // +100ms
const prefs = await getPrefs(userId); // +100ms = 300ms total
// Parallel: all start simultaneously — fast
const [user, orders, prefs] = await Promise.all([
getUser(userId), // }
getOrders(userId), // } all start at once
getPrefs(userId), // } ~100ms total
]);
Promise.allSettled: When Some May Fail
// Promise.all rejects if ANY promise rejects
// Promise.allSettled waits for all, reports success/failure per item
const results = await Promise.allSettled([
fetchPrimaryData(),
fetchOptionalData(), // OK if this fails
]);
results.forEach(result => {
if (result.status === 'fulfilled') console.log(result.value);
if (result.status === 'rejected') console.warn(result.reason);
});
Common Mistakes
// Forgetting await — returns Promise, not value
const user = getUser(userId); // Bug: user is a Promise
console.log(user.name); // undefined
// Unhandled rejection (crashes Node.js)
someAsyncFn(); // Missing await AND missing .catch()
// Fix: always await or chain .catch()
await someAsyncFn();
someAsyncFn().catch(handleError);
TypeScript catches many async mistakes at compile time — the strict setup in the AI SaaS Starter Kit has strictNullChecks and proper async error handling configured from day one.
Top comments (0)