The Trust Problem: Inversion of Control
What inversion of control actually means when you hand a callback to someone else's code, and the real bug it causes when that code doesn't behave the way you assumed.
Here's the actual idea this post teaches, in one sentence: when you hand your callback to another function, that function decides when to call it and how many times, not you, and nothing in JavaScript checks whether it does that correctly.
That's the real cost hiding under callback hell that has nothing to do with nesting. That handoff has a name: inversion of control. You had control of your own code. Now some other function has it instead.
someFunction(callback);You write callback. But you never call it. someFunction does. Whatever someFunction decides, whenever it decides it, however many times it decides to, that's what happens to your code.
Most of the time this is completely fine. setTimeout always calls your callback exactly once. So does almost everything you'll ever use. You've been trusting this for years without ever thinking about it, and you've been right to.
But that trust is never actually checked. And the one time it's wrong, nothing warns you.
What It Looks Like When That Trust Breaks
Say you're building a checkout page. Right after the customer clicks "Pay," your code calls a service run by a different company, one that logs the sale. When it's done, it calls your callback, and your callback charges the card.
trackPurchase(orderDetails, function () {
chargeCard(orderDetails);
showThankYouPage();
});It works every time you test it. You ship it.
Six months later, a customer named Neal gets charged five times for one order.
You trusted trackPurchase to call your callback exactly once. It didn't. Nothing in your code ever checked for that, because a plain callback gives you nowhere to check it.
It's Not Just "Called Too Many Times"
The fix seems obvious: remember that it already ran.
let alreadyCharged = false;
trackPurchase(orderDetails, function () {
if (alreadyCharged) return;
alreadyCharged = true;
chargeCard(orderDetails);
showThankYouPage();
});That patches the one thing Neal's bug exposed. But "called too many times" was only one of several things you were trusting, without knowing you were trusting them.
Every callback you've ever written was quietly hoping all five of these would go right. Most programs never check even one.
Why You Can't Just Add More Checks
You could write a check for each of these five things, for this one function. That's doable, once.
A real app has hundreds of callbacks, not one. Checking all five things, by hand, in every single one of those hundreds of places, is the same manual work as coordinating three callbacks, just repeated hundreds of times.
That's the real problem underneath callback hell: there's no shared, reliable way to make a callback keep its promises. Every program has to trust it, one callback at a time, with no way to check.
The next post covers the second real problem, one that has nothing to do with trust: why nesting callbacks breaks the way your brain naturally follows code.
Keep reading