In praise of callbacks


People talk about callbacks as if they are some sort of programming conundrum, because of "callback hell". The Parse bloghas a nice example:

Parse.User.logIn("user", "pass", { success: function(user) { query.find({ success: function(results) { results[0].save({ key: value }, { success: function(result) { // the object was saved. } }) } }) }});

And they offer up the well known solution, Promises:

Parse.User.logIn("user", "pass").then(function(user) { return query.find();}).then(function(results) { return results[0].save({ key: value });}).then(function(result) { // the object was saved.})

But is that actually more readable than just breaking the callbacks all into the same scope?

Parse.User.logIn("user", "pass", thenFind)function thenFind(user) { query.find(thenSave)}function thenSave(results) { results[0].save({key: value}, etc)}function etc(result) { // the object was saved}

Maybe for some data flows one or the other is more readable. I don't know. It's certainly not night and day to me. And I've been using the latter style for a while and it feels more expressive to me. The sub-functions are often the first step on the way to a fully separated function or module. And those extra function names mean you get a really nice, readable stack trace wherever you are.

At a high level, I also dislike that promises allow function references to sort of leak everywhere. With callbacks, you see what function gets passed in, which gives you some information about who called you. And the stack is intact... you weren't called by some promise library popping objects off a queue. The callback also tells you, without a doubt, what will happen next. With promises you never really know. You "resolve" and then it's not always clear what happens next. Or if you throw an error any of a number of things could happen. With promises you have quite a lot of ways your function could exit.

With callbacks, there are just two: calling out into another function, or returning to the one who called you.

What this means is if you are confused about how something is going down, you just start at the top of your stack and work your way down the chain.

This whole discussion reminds me of the transition I made from PHP to Rails. Of course, Rails was a revelation to me. I had never really thought about application architecture before, and Rails is a solid set of opinions on the subject. But I remember struggling with the router for so long. A route wasn't getting through to the right controller and I couldn't figure out why. My route file looked good. The controller was in the right folder, with the right extension... why wasn't Rails picking it up?

For all its warts, this was never a problem in PHP. You just look at path of the URL, find the matching folder, open the file, start at the top and work your way down.

I think the web development community, and framework authors in particular, underestimate the value of code that has this property.