Skip to content

Instantly share code, notes, and snippets.

@erikpukinskis
Last active October 22, 2015 07:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erikpukinskis/bf800c365e74bcba9f6f to your computer and use it in GitHub Desktop.
Save erikpukinskis/bf800c365e74bcba9f6f to your computer and use it in GitHub Desktop.

In praise of callbacks

People talk about callbacks as if they are some sort of programming conundrum, because of "callback hell". The Parse blog has 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.

@ikari-pl
Copy link

Well, it is worth remembering, that Promises look just as bad until you do the same - extract your handlers of each step to a function. Really, always, no matter whether callbacks or promises. Then you get:

Parse.User.logIn("user", "pass")
  .then(find)
  .then(save)
  .then(etc)

We should all make our lives easier by extracting and naming each small piece of code.

@tcurdt
Copy link

tcurdt commented Oct 21, 2015

Fair point - but remember that Promises also are a form of caching.

var compiled = new Promise(...)
compiled.then(...)
compiled.then(...) // does not compile again

They represent a state in the future and are not just a calling pattern.

@Amberlamps
Copy link

And those extra function names mean you get a really nice, readable stack trace wherever you are

You get the same effect if you name your promises accordingly.

With promises you never really know.

When you are not comfortable with promises, you probably should not use them. But if you do understand how they work, you always know.

Or if you throw an error any of a number of things could happen.

With promises you actually only need a single error handler. If you use callbacks, you have to check on every callback whether an error occurred.

@tcurdt: That is a great extra point. With promises you do not need to care about when and where you asynchronous call is executed and/or returned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment