Callbacks, Promises & Async/Await
25 min read
CallbacksPromisesAsync/AwaitPromise.allError HandlingParallel ExecutionTimeout Patterns
The Evolution of Async JavaScript
1. Callbacks (The Original Way)
function fetchUser(id, callback) {
setTimeout(() => {
callback(null, { id, name: "Alice" });
}, 1000);
}
fetchUser(1, (err, user) => {
if (err) return console.error(err);
console.log(user);
});Callback Hell
// ❌ The Pyramid of Doom
getUser(id, (err, user) => {
getOrders(user.id, (err, orders) => {
getOrderDetails(orders[0].id, (err, details) => {
getProduct(details.productId, (err, product) => {
console.log(product); // 4 levels deep!
});
});
});
});2. Promises (ES6)
const fetchUser = (id) => new Promise((resolve, reject) => {
setTimeout(() => {
if (id) resolve({ id, name: "Alice" });
else reject(new Error("Invalid ID"));
}, 1000);
});
fetchUser(1)
.then(user => getOrders(user.id))
.then(orders => getOrderDetails(orders[0].id))
.then(details => getProduct(details.productId))
.then(product => console.log(product))
.catch(err => console.error(err)) // Catches ANY error above
.finally(() => console.log("Done")); // Always runsPromise States
| State | Meaning | Can Transition To |
|---|---|---|
| Pending | Initial state | Fulfilled or Rejected |
| Fulfilled | Operation succeeded | None (settled) |
| Rejected | Operation failed | None (settled) |
3. Async/Await (ES2017)
async function getProductForUser(id) {
try {
const user = await fetchUser(id);
const orders = await getOrders(user.id);
const details = await getOrderDetails(orders[0].id);
const product = await getProduct(details.productId);
return product;
} catch (err) {
console.error("Failed:", err.message);
throw err; // Re-throw to let caller handle it
}
}Promise.all vs allSettled vs race vs any
| Method | Resolves When | Rejects When | Use Case |
|---|---|---|---|
Promise.all | All fulfill | Any rejects | Parallel tasks, all required |
Promise.allSettled | All settle | Never | Independent tasks, need all results |
Promise.race | First settles | First rejects | Timeout patterns |
Promise.any | First fulfills | All reject | Fastest success from multiple sources |
// Parallel execution (fast)
const [user, posts, comments] = await Promise.all([
fetchUser(1),
fetchPosts(1),
fetchComments(1)
]);
// Timeout pattern
const result = await Promise.race([
fetchData(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout")), 5000)
)
]);Common Mistakes
// ❌ Sequential when it could be parallel
const a = await fetch("/api/a");
const b = await fetch("/api/b"); // Waits for `a` to finish!
// ✅ Parallel
const [a, b] = await Promise.all([
fetch("/api/a"),
fetch("/api/b")
]);
// ❌ Forgetting error handling
async function bad() {
const data = await riskyOperation(); // Unhandled rejection!
}
// ✅ Always handle errors
async function good() {
try {
const data = await riskyOperation();
} catch (e) {
handleError(e);
}
}