What Callback Hell Actually Is
Why callback hell has almost nothing to do with nested indentation, proven by rewriting a nested callback so it isn't nested at all.
Here's the actual idea this post teaches, in one sentence: callback hell isn't caused by nesting, it's caused by not being able to trust a callback to behave and by nesting being the only way to show one step waiting on another, and both of those stay just as broken even when the code isn't nested at all.
I used to think callback hell just meant "too much indentation." Flatten the code, problem solved. That's wrong, and I can prove it: I'll remove every bit of nesting below and the exact same problem stays.
A Callback Splits Your Code Into Now and Later
Take the simplest possible callback:
function afterTimer() {
console.log('Cart saved');
}
setTimeout(afterTimer, 1000);Most people would describe this as "wait one second, then log a message." That description misses something important.
This one line of code splits your whole program into two halves.
The first half is everything up to setTimeout(afterTimer, 1000). It runs right now.
The second half is everything inside afterTimer. It runs later, whenever the timer decides to call it.
That's why a callback is sometimes called a continuation. It's the second half of your program, packed into a function and handed off to run later.
You don't get to control exactly when "later" happens. You only know it happens somewhere further down the one thread your whole program runs on.
The Nesting Everyone Blames
Here's the classic shape everyone means when they say "callback hell": three steps, each one waiting on the last.
setTimeout(() => {
console.log('Step 1');
setTimeout(() => {
console.log('Step 2');
setTimeout(() => {
console.log('Step 3');
}, 300);
}, 300);
}, 300);That staircase of nested functions, three levels deep, is the "pyramid" people blame for callback hell.
Now watch what happens when the exact same three steps are rewritten with named functions instead of nesting them inline.
function step1() {
console.log('Step 1');
setTimeout(step2, 300);
}
function step2() {
console.log('Step 2');
setTimeout(step3, 300);
}
function step3() {
console.log('Step 3');
}
setTimeout(step1, 300);No pyramid. No staircase. Just three plain functions, each one starting the next.
Run both versions and you get the identical output, in the identical order, on the identical delay. The second version just isn't shaped like a pyramid anymore. Nothing about what the code actually does changed, only how it looks on the page.
The Real Problem Was Never the Shape
If indentation isn't the problem, something else has to be. Two things, actually.
Neither problem cares whether your code is nested or flat. A flat callback can still call you the wrong number of times. A flat callback can still hide a step your brain has to hunt for across three different functions. Flattening the shape never touched either one.
That's also why the callback is only the first of six tools in this series, not the only one.
The next two posts take each of those problems in turn, starting with trust: what happens when the thing you hand your callback to doesn't behave the way you assumed it would.
Keep reading