Problems with Callback-Based Code:
1️⃣ Callback Hell (Pyramid of Doom)
Nested callbacks become hard to read and maintain.
login(user, () => {
getProfile(() => {
getPosts(() => {
getComments(() => {
// messy
});
});
});
});
2️⃣ Poor Error Handling
Errors must be handled at every level.
doTask((err, res) => {
if (err) {
// handle error
}
});
Hard to manage across multiple async steps.
3️⃣ Inversion of Control
You give control of execution to a callback that may:
- execute multiple times
- never execute
- execute with wrong arguments
This can cause bugs.
4️⃣ Difficult Composition
Running tasks sequentially or in parallel is complex.
✅ How Promises Solve These Problems
✔ 1. Readable Chaining
Promises allow linear .then() chains.
login(user)
.then(getProfile)
.then(getPosts)
.then(getComments)
.catch(err => console.error(err));
✔ No nesting
✔ Easy to follow
✔ 2. Centralized Error Handling
One .catch() handles errors from the entire chain.
promiseChain.catch(handleError);
✔ 3. Better Control
Promises guarantee:
- resolve or reject only once
- predictable execution
✔ 4. Easy Composition
Promises provide helpers:
Promise.all()
Promise.race()
Promise.any()
Allow parallel and race conditions easily.
✔ 5. Foundation for async/await
Promises enable even cleaner async code:
try {
const user = await login();
const posts = await getPosts(user);
} catch (err) {
console.error(err);
}
🎯 Short Interview Answer
Callback-based code suffers from callback hell, poor error handling, inversion of control, and poor composability.
Promises solve these problems by providing clean chaining, centralized error handling, predictable execution, and utilities for composing async operations.
They also enableasync/await, making async code even more readable.