AddThis

Sunday, April 7, 2013

Callback versus Promises


I like Node.  I like the callback mechanism.  I find the approach to multithreading very interesting.  That being said, I should preface that I also like Java's Futures/FutureTasks.  I guess what I'm trying to say is that I like things where I don't have to think about forking and joining (I'm ok with Java7's ForkJoinExecutor because again, I don't have to do it myself).  Just do it for me and give me the result when you get it.  In the meantime I'm going to make myself a sandwich, or whatever else I'm going to do while you're out fetching me whatever it is I asked for.

As such, this post got my attention:
http://blog.jcoglan.com/2013/03/30/callbacks-are-imperative-promises-are-functional-nodes-biggest-missed-opportunity/

James Coglan's basic assertion is that Node's decision to go with the callback mechanism was a mistake.  Before I go much deeper, I'm going to summarize a bit just in case you tl;dr it.  Essentially callbacks is at it's core an imperative construct while Promises (essentially FutureTasks) are more functional.  Why do we like functional versus imperative again?  Well because imperative tells the computer HOW to do something while functional tells the computer WHAT to do.  Imperative programming mutates state of objects as those objects are passed from function to function.  This means our programming is very dependent on the order of method invocations as well as the exact state of the objects over a given time.  Functional programming deals with values.  Arguments are values, and results returned from functions are values.  Nothing is holding state.  This essentially lets you parallelize a lot more because you never have fear or two threads potentially mutating the same object concurrently.  For any given input, you will ALWAYS get the same result.  1+1 is always 2.  But varX + varY could be 2 if varX and varY are both holding 1 at the time of execution.  But let's say this method then mutates varX.  Uh-oh, now depending on when you call this imperative method, you could get 2 or 3.  Get it?  Kind of?  Good enough.

So why did Node go callback?  Well because it's easier to understand.  Promises are much more abstract.

Callback
makeSomeBlockingCallWithCallback(callback);
This is easy to understand, you make some blocking call and when it's done, the callback method is called.

Promise
var promise = function() {
  var p = new Promise();
  var results = makeSomeBlockingCall(); 
  p.resolve(results);
  return p;
}
promise.then(callback);
I tried to do this with as little code as I could, but even without error handling, its longer than the callback example.  But let me explain what's going on here.  You create a promise object, run some blocking code, then shove the results of that onto the promise and return it.
We can chain together functions by using the then, which allows us to call our callback.

So what's the advantage of doing this?  There's a lot of extra code and the end result is the same, isn't it?  Not quite.  The first thing is that you avoid the infamous callback hell.  If you wanted to chain using the standard callback way, you'd have something like

makeSomeBlockingCall(function() {
  anotherLongCall(function() {
    yetAnotherLongCall(function() {
      console.log('im in callback hell!')
    });
  });
});

This happens quite a lot in node, think database work.  You open a connection, if it was successful write, after done writing, say you update a file or something.

For the Promise approach, you just have promises that all chain.
promiseForSomeBlockingCall
  .then(promiseAnotherLongCall)
  .then(promiseYetAnotherLongCall)
  .then(function() {
    console.log('less callback helly');
  });

So there's a slight improvement there in readability.  But if the only reason you're using promises is to reduce your callback hellness, you're missing the point of all this.  Remember what I said before about imperative versus functional programming?  The promises approach does not mutate the arguments.  Each promise does something and after it's done, returns a value into the next call in the promise chain.  If anyone of them fails, then the chain is broken (this is desired, if you can't connect to the db, you shouldn't write to it).

This is not the case with callbacks.  The only way data can get into a callback is via argument.  That means that all behavior is done as a result of a side effect (a side effect is when you mutate the argument).  You are no longer dealing with objects as values, objects now hold state that mutate over time.  This reduces what we can do concurrently as we are now very dependent on the order of method invocations and are extremely sensitive to the state of the variables as they change over time.

So how does this effect you?  Well jQuery now has Promises (http://api.jquery.com/promise/) and Deferred objects (http://api.jquery.com/category/deferred-object/).  A deferred object is like a promise but has no resolve or reject method (resolve is used to store the results, reject is used to break the chain).

Callbacks are great and easy to understand but let me just say on your next javascript project, you should go ahead and give promises a shot and see what you think.

No comments: