JavaScript Promises vs Callbacks: Escaping Callback Hell

Before Promises, asynchronous JavaScript meant nested callbacks — and deeply nested async logic (“callback hell”) was infamous for becoming unreadable fast.

// Callback hell: each step nests one level deeper
getUser(userId, (user) => {
    getOrders(user.id, (orders) => {
        getOrderDetails(orders[0].id, (details) => {
            console.log(details); // four levels deep and growing
        }, handleError);
    }, handleError);
}, handleError);
// Promises: flat chains instead of nesting
getUser(userId)
    .then(user => getOrders(user.id))
    .then(orders => getOrderDetails(orders[0].id))
    .then(details => console.log(details))
    .catch(handleError); // ONE error handler for the whole chain

Promises’ biggest practical win over callbacks isn’t just readability — it’s that error handling collapses to a single .catch() at the end of the chain, instead of needing an error callback threaded through every single nested step.