Why do promises make async JavaScript better than callbacks?

This is an episode of Thoughts on Functional Programming, a podcast by Eric Normand.

Subscribe: RSSApple PodcastsGoogle PlayOvercast

Promises are more popular than ever. They make our code better than callbacks. But why? In this episode, I dive deep into why promises are better than callbacks, and it's not just about indentation.

Transcript

Eric Normand: Why do Promises make async JavaScript better than callbacks? By the end of this episode, you'll get a deeper look into why Promises feel so much better than callbacks when you're using them. This will all be from a functional programming perspective. My name is Eric Normand. I help people thrive with functional programming.

This is an important topic because I think Promises are great and they hold a lot of lessons for why or how we can build our own concurrency primitives. What makes Promises so special? Maybe it'll just give you a deeper insight into why you like them as well.

I have a few points I'd like to go over. Promises represent a coordination point. They're often talked about as they represent a future value and that's true. They represent a coordination point between two disparate parts of your code.

There's the code that, it's almost not even your code, you'll do an AJAX request, and you get a Promise back. At some point, this AJAX request is going to finish and put the answer, the response into that Promise.

At the same time, there's another piece of your code which is what you need to run with that answer. There's two pieces of code, the HTTP request that is coming in asynchronously and your code. There's a coordination point because you don't know which one is going to happen first.

Is the response going to come back before my code is ready for that value or is my code that's ready for the value going to be there first and it's going to wait for the response?

The thing is, just by the nature of asynchronous code, asynchronous mode of execution, you can't know. It's going to be different every time. It's going to be different every time which one comes in first, how long each one takes.

One thing that happens with callbacks...Callback is just a function that you pass that will be called later with the value, with the answer. Because it's called later, you're actually giving up control. You're saying, "Hey, just call this when you are ready."

You're telling that to the HTTP request, "Call this with the answer when you're done." You might not be ready yet like the main thread. You might not be ready, the main timeline, let's call it.

You're giving up control, you're giving it back to the AJAX request, and it's going to control when this thing gets called. Promises give you back that control. It's a compromise because the AJAX request does still need some control. You also need control, too, in your code to make sure that things happen correctly.

Just as an example, if you need to do, let's say three AJAX requests, in typical a callback style, it is so much easier. If you need the answer to all three, it's so much easier to just do one, wait for the response, and then in the callback, you do the second one.

Then in the callback for that one, you do the third one, and then you have a final callback where you have all the answers you need.

This is inefficient because instead of doing all three requests at the same time, which could be possible because they don't need any data from each other. You can do all three at the same time, then wait for all three to be done before you continue processing. There's a lot of time that you can overlap, so it will cut down on the total amount of time.

Promises let you do that really easily, and it's because you have that control back. That, "I'm not ready for this value yet, but I'm gonna get it started. I'm gonna start these three AJAX things, and then when I'm ready, potentially block, I will read them from the Promises."

Another thing that Promises do, which is going a little bit deeper, is that they're composable. You can't really compose callbacks. Yes, they are functions and you can compose functions but not in the way that callbacks work. When you compose two functions, you're making a third function that is the composition of the other two.

That doesn't really make sense for callbacks because what's important in a callback is that it will continue the execution of the program when some value is ready, whereas Promises make that a thing that you can compose.

Built into the Promises model, you can return a Promise that's not even ready yet. You can return a Promise from a Promise, and that will chain the Promises together. There's this automatic composition model going on. You can also do interesting stuff like call Promise.all, which will wait for all Promises in an array to be done before continuing on.

There's other ways to compose them like that that are based on this same idea of returning Promises. The reason that can be done is that Promises are first-class. They are objects that you can treat like values. You can pass them around. You can return them from functions. You can pass them as arguments. You can save them to variables. They are first-class.

Callbacks, really the only thing you can do with them is call them, and you don't want to call them, you want to pass them to an AJAX function or something. By making them first-class, you now have the whole language available to you to manipulate them. They're like a first-class coordination point. That's where all of its power comes from.

Finally, one thing that people often talk about is how it gets rid of the indentation. At first glance, that seems not really hitting very deep, it's a shallow concern, it's just like syntax. It's true. It's just a shallow concern.

If you grant the people who are saying this, the benefit of the doubt and they're trying to touch at something deeper, what they're saying is really true. That it is taking some nested idea and bringing it to the surface.

Normally, if you're using callback code, you get this. If you just have a linear chain of callbacks like, "I call this AJAX function, when the response comes back it calls this callback, and I make another AJAX, and I call another AJAX." It just nests over and over, deeper and deeper.

What the Promise lets you do is turn that into a linear thing. [indecipherable 8:16] a little, but it's all on the same level, you're not going further and deeper each time. That does help our code, it does make it look easier, and it's easier to read. More importantly, it's that the nested nature of it is captured in the structure of the Promise.

This is how the promise works. That nested thing that you're trying to do call this and then when that's done, call that and then when that's done, call that, becomes just part of the call chain of then function, of then methods, that are called on it. It's capturing some essential thing in an ergonomic way.

That's an important feature of it. This common idea of callback chains has become linearized. It shows that there's something about the structure of Promises that mirrors that same indentation thing but in a better way.

I'm going to recap. There's actually five different points here. Promises are a coordination point between the asynchronous call that you make and its result and your code. It coordinates because you don't know who's going to happen first.

You don't know if the AJAX request is going to come back first, or if your code is going to be ready for the answer first, so we need a point where that decision can be ignored. It doesn't matter who's first. It's going to figure out when to run what.

Promises do that by giving you back the control that you lost with callbacks. You gave up the control to the async call when you shouldn't have given up all the control. Promises give you the control you need back. They're composable, Promises are composable which is a huge deal, and they do that by being first-class. They are objects that you can pass around.

They do help you with your indentation. That touches at a deeper idea that you can actually see the structure of the callback chain and indentations, and the Promise has said, "No, let's take that indented structure, that linear structure, and turn it into an object and method calls that feels better, that captures exactly what we're trying to do here, this common thing."

Do yourselves a favor. If you don't know Promises in JavaScript, go check them out. They can really transform your code. They can turn an ugly mess into something more reasonable, let's say.

Do me a favor. If you've liked this talk, I like to go deep, I like to think about these things more than just the surface level. If you liked this, then you should subscribe because there's more like this coming.

If you've got questions, if you have an opinion, a different opinion on why Promises are so great, I love to get into discussions about this, so email me at eric@lispcast.com. You can also find me on Twitter. I love to get into discussions on Twitter, I'm @ericnormand with a D, and you can find me on LinkedIn. Let's connect, just send me a little message. All right. Take care.