What is callback hell?

JavaScript programmers talk a lot about callback hell. In this episode, we look at the phenomenon from a functional programming perspective.


Eric Normand: What exactly is Callback Hell? Hi, my name is Eric Normand. These are my thoughts on functional programming.

It's common in the JavaScript world to talk about Callback Hell. Casually people will talk about how, when you have all these callbacks, you have to start indenting and indenting and indenting. It becomes hard to keep track of where you're at...

You're going further and further to the right in your editor, and your lines are getting long, and if you've got a line length limit, you now have to make your line shorter and shorter. All of those are a pain.

When people refer to that as Callback Hell, they're actually missing the point. One of the things that has been proposed as a solution to Callback Hell is Promises, and with Promises it basically lets you chain. Instead of nest, it lets you chain.

When you chain, you can go straight down at the same indentation level. That is nice, because you don't have this major problem of ever increasing indentation. However, it doesn't solve the deeper hell of callbacks. It's the same hell.


The deeper hell of callbacks is that you have totally given up control of when your callback is going to be called. Every time you call something that has a callback that could be called anytime later, it's like you're creating a different timeline. It's like you're creating different threads.

JavaScript doesn't have threads, and that was probably a good choice. In fact, JavaScript has a pretty cool concurrency model, meaning if you have the thread it's yours. You can just run with it as much as you want, but as soon as you call something that is asynchronous, you give up that control.

That works great for a lot of things, because you know that while I'm modifying this variable, nothing else is going to have the thread, and I'm going to be fine. When they're reading it, nothing else has the thread, so they know nobody's modifying it. That is really nice.

The problem is as soon as you need to do something with a callback, you've actually introduced a new timeline. The current thread is going to keep going, and then this callback is going to happen at a different time, sometime later, but you can't say when.

That callback might have a nested callback in it, and that callback might have a nested callback, and all of those are going to happen in order, so they're in the same timeline, in order. Now, if you have multiple of those going on at the same time, and very likely you do...

If you're in a JavaScript environment where a button click is a callback and an AJAX request has a callback, you maybe have four AJAX requests going at the same time. You're going to have all these timelines. All of those could interleave so that actions in each of those timelines could interleave.

Now, you don't have the possibility of that, "I got the thread. I'm gonna hold on to it." You still have that while you've got the thread, but anytime you do a call...You still have this...I'm going to read this value.

I'm going to read the value from a variable. Let's say it's a global variable. I'm going to read the value. I'm going to make an AJAX request to the server based on that value. The server's going to give me an answer, and I'm going to write that into the variable.

However, in the meantime, since I've done that request, while the request was in-flight, another thread read that variable, made a decision, and wrote back to it. This is entirely possible, and it's the exact same concurrency problem that we had before, before we had threads.

Even though [laughs] JavaScript has one thread, we still have this multiple timeline sharing problem, sharing a common resource. That's one aspect to Callback Hell. It's that it reintroduces threading.


The other one is if you have to do multiple AJAX requests and then reassemble the answer into something coherent, the best thing to do in a callback world is do them one at a time. However, that's not the most efficient.

You know you need to do all three, to send them all out at the same time, that the server handle all three and respond. The problem is they each have their own independent callback. Promises can help with this very simplistic scenario where you want to get all. There's a Promise.all() method, and so it'll just wait for all of them to come through.

Then there's another thing called Promise.race(), which is where you have two callbacks. It's a simple case. You have two callbacks, and you want the first one that comes back. You do a Promise.race() on the two of them, and it will return a new Promise that is the first one that comes back.

However, what about that second callback? What if you want both? You just want them in the order that they come back in but in the same thread. You've seriously limited what you can do.

There's all these little scenarios where you've got Promises within Promises, where you're still in hell. It just pushes the hell off a little bit longer. That's Callback Hell.

My name is Eric Normand. You can reach me on Twitter @ericnormand. I'm also available over email, eric@lispcast.com. Please subscribe to this, however you subscribe, whether that's on YouTube or on a podcast catcher. Thank you so much for listening. I'll see you next time.