Skip to main content

Promises & Callback Hell

Introduction​

Callbacks work, but they can quickly become messy. When you need to run multiple async tasks in sequence, you often end up with nested callbacks inside callbacks. This is called callback hell.

Callback hell is when your code looks like a pyramid of nested functions, making it:

  • Hard to read
  • Hard to maintain
  • Easy to break

Promises were introduced to solve this by flattening async code.


Concepts​

  • A Promise represents a value that will be available in the future.
  • States: pending → fulfilled (resolved) or rejected.
  • Use .then() for success, .catch() for errors, .finally() for cleanup.

Examples​

Callback Hell

// Nested callbacks (callback hell)
getUser(function(user) {
getOrders(user.id, function(orders) {
getOrderDetails(orders[0], function(details) {
console.log("First order details:", details);
});
});
});

// Hard to read and maintain

Promises

// Using promises instead
getUser()
.then(user => getOrders(user.id))
.then(orders => getOrderDetails(orders[0]))
.then(details => console.log("First order details:", details))
.catch(err => console.error(err));

Much cleaner — no deep nesting.


Challenges​

1. Promise resolves after 1s​

  • Create a promise that resolves after 1 second with the message "Hello World".
💡 Show Answer
new Promise(res=>setTimeout(()=>res("Hi"),1000)).then(console.log);

2. Reject after 2s​

  • Write a promise that rejects with "Error: Failed" after 2 seconds.
  • Handle it with .catch().
💡 Show Answer
new Promise((_, rej) => setTimeout(() => rej("fail"), 2000)).catch(
console.error
);

3. Chain transforms​

  • Chain.then() calls to transform a number (e.g., square it, then double it).
💡 Show Answer
Promise.resolve(2)
.then((n) => n * 2)
.then((n) => n * 3)
.then(console.log);

4. wait(ms)​

  • Write a wait(ms) function that returns a promise that resolves after ms milliseconds.
💡 Show Answer
function wait(ms) {
return new Promise((res) => setTimeout(res, ms));
}
wait(500).then(() => console.log("done"));

5. Promise.all​

  • Use Promise.all() to run two promises in parallel and log both results.
💡 Show Answer
Promise.all([
Promise.resolve("a"),
Promise.resolve("b")
]).then(console.log);

6. Promise.race​

  • Use Promise.race() to log whichever promise finishes first.
💡 Show Answer
Promise.race([
new Promise((r) => setTimeout(() => r("slow"), 1000)),
Promise.resolve("fast"),
]).then(console.log);

7. Wrap setTimeout​

  • Rewrite a callback-based setTimeout into a promise-based function.
💡 Show Answer
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

// Usage example:
delay(1000).then(() => {
console.log('1 second has passed');
});

8. Random resolve/reject​

  • Write a promise that randomly resolves or rejects, then handle both with .then() and .catch().
💡 Show Answer
const randomPromise = new Promise((resolve, reject) => {
const success = Math.random() > 0.5;
setTimeout(() => {
if (success) {
resolve('Promise resolved!');
} else {
reject('Promise rejected!');
}
}, 1000);
});

randomPromise
.then(result => {
console.log('Success:', result);
})
.catch(error => {
console.log('Error:', error);
});

9. fetchUser then orders​

  • Create a fetchUser() promise and a fetchOrders(user) promise.
  • Chain them so that orders depend on the user.
💡 Show Answer
function fetchUser() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: 1, name: 'Alice' });
}, 500);
});
}

function fetchOrders(user) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ orderId: 101, userId: user.id, item: 'Book' },
{ orderId: 102, userId: user.id, item: 'Pen' }
]);
}, 500);
});
}

// Chaining the promises
fetchUser()
.then(user => {
console.log('User:', user);
return fetchOrders(user);
})
.then(orders => {
console.log('Orders:', orders);
})
.catch(error => {
console.error('Error:', error);
});

10. finally demo​

  • Show .finally() by logging "Task finished" after any outcome.
💡 Show Answer
const myPromise = new Promise((resolve, reject) => {
// Simulate async task
setTimeout(() => {
// Change to resolve() or reject() to test both outcomes
resolve("Success!");
// reject("Error!");
}, 1000);
});

myPromise
.then(result => {
console.log("Result:", result);
})
.catch(error => {
console.log("Caught:", error);
})
.finally(() => {
console.log("Task finished");
});