2014.01.12

Callbacks, Promises, Signals and Events

Sometimes I get involved in discussions on twitter and Facebook related to development. This week I ended up sending a link to an old(ish) twitter thread explaining when to favor Promises/Callbacks and when to use Signals/Events for asynchronous operations. I think this topic deserves further explanation.

Callbacks

Callbacks are commonly used when you have an asynchronous operation that should notify the caller about its’ completion:

var toppings = {cheese: true, bacon: true};

function orderPizza(data, done) {
  // send info to server (which might take a few milliseconds to complete)
  xhr({
    uri: '/order/submit',
    data: data,
    complete: function(status, msg, response) {
      if (status !== 200) {
        // notify about error
        done(new Error(status +': '+ msg));
      }
      // notify about success
      done(null, response);
    }
  });
}

// the method "showOrderStatusDialog" will be called after we get the response
// from the server
orderPizza(toppings, showOrderStatusDialog);

function showOrderStatusDialog(err, response) {
  if (err) {
    showOrderFailure(err);
  } else {
    showOrderSucces(response);
  }
}

Callbacks are great for cases where you have an action that triggers a direct reaction (eg. animation, ajax, batch processing).

PS: technically, a callback is any function that is passed as an argument to another function and that is executed later, be it synchronously or asynchronously. But lets keep the focus at async actions that requires a response.

Promises

Promises are a replacement for Callbacks to help you deal with composition of multiple async operations while also making error handling simpler in some cases.

function orderPizza(data) {
  // the xhr in this case returns a promise and handle the error logic
  // internally; it shifts the complexity to the xhr implementation instead
  // of spreading it through your app code
  return xhr({
    uri: '/order/submit',
    data: data
  });
}

// first callback of `then` is success, 2nd is failure
orderPizza(toppings).then(showOrderSucces, showOrderFailure);

There are 2 really cool things about promises:

  1. they are only resolved once;
  2. then method returns another promise and you can return data from the
    callback to map the value received by the next callback in the chain;

Since promises are only resolved once they can be used for smart caching or for cases where you might have multiple calls trying to execute same action (which should only happen once).

var _cache = {};
function getOrderInfo(id) {
  if (! _cache[id]) {
    _cache[id] = xhr({
      uri: '/order/'+ id
    });
  }
  return _cache[id];
}

getOrderInfo('123').then(...);

// .... later in the code

// since we already asked for the order "123" info it won't do another XHR
// request, it will just use the cached promise and execute callbacks whenever
// the promise is resolved; if it was already resolved it will execute callback
// on the nextTick
getOrderInfo('123').then(...);

And you can compose multiple async operations.

orderPizza(toppings)
  // if `processStatus` returns a promise the `showOrderSucces` will only be
  // called once the promise is "fullfilled", this makes composition of
  // multiple async operations very easy;
  // if the value returned by `processStatus` is not a promise it will be
  // passed directly to the `showOrderSucces` (similar to Array#map)
  .then(processStatus)
  .then(showOrderSuccess, showOrderFailure);

PS: There is a proposal to add Promises to the EcmaScript spec, but it’s generating a lot of discussions – some people think this kind of abstraction should live in “user land”.

Signals

Signals are not a replacement for Callbacks/Promises! They have a totally different purpose. Signals are used as a way of communication between objects. They are mainly used for cases where you need to react to actions that you are not responsible for triggering. These events usually happens multiple times during the application lifetime and might be dispatched at random intervals.


// you register listeners to each discrete signal and `delivery` object doesn't
// need to know about the existence of your object (or how many objects are
// listening to a specific event)
delivery.leftBuilding.add(userNotifications.dispatched);
delivery.arrivedAtLocation.add(userNotifications.arrival);
delivery.succeed.add(userNotifications.success);
delivery.failed.add(userNotifications.failure);

Anything that happens multiple times and/or that might be triggered at random intervals should be implemented as Signals/Events.

The main advantage of Signals over Events is that it favors composition over inheritance. Another benefits are that discoverability is higher (easier to identify which events the object dispatches), and it also avoids typos and/or listening to the wrong event type; since trying to access an nonexistent property throws errors - which usually helps to spot errors earlier and simplify the debug process.

Events

EventEmitters are basically an Object that creates/dispatches multiple Signals types on-demand and usually use Strings to define the message name.

The biggest advantage of Events over Signals is that you can dispatch dynamic events during runtime - suppose you want a different event type for each change on your Model class.

node.js uses EventEmitter internally a lot but creates some callback-like APIs to abstract/simplify the process, like the http.createServer method:

http
  .createServer(respondToRequest)
  .listen(1337, '127.0.0.1');

Is exactly the same as:

var server = http.createServer();
server.on('request', respondToRequest);
server.listen(1337, '127.0.0.1');

So even tho it looks like http.createServer takes a regular done Callback (like described on my first examples), it is in fact an event listener. - I do not recommend this kind of API since at first look user would think that http.createServer argument would be executed after its “creation” and not at each request.

I hope it’s clearer when/how to use the 4 patterns!

PS: I usually favor Signals over EventEmitter since I believe it has some very strong benefits. I’m planning to code and release v2.0 of js-signals with some improvements, slightly different API and killing some of the bad features. Feedback and pull requests are highly appreciated!

Edit 2014/01/17: improved Signals description to describe better the use cases and advantages/differences.


Comments

Re: our Twitter discussion

Events/signals are a great way for host applications to allow third party plugin developers to build functionality on top of the application's core. They expose hook points allowing you to register a "listener" function to be executed at a particular point in time with the necessary arguments.

The problem with callbacks/events/signals (as opposed to promises) is that they don't notify you when the user's registered function has completed (assuming it's async).

For example, I'm working on a site generator that emits various events to plugin developers like: beforeRenderPost, afterRenderPost, beforeWritePost, and afterWritePost. When all your code is synchronous this works just fine, but as you can see if the listener you register on afterRenderPost doesn't complete before the post is written to disk, there's nothing you can do about it, and the host application has no idea.

I'm brainstorming a good solution to this problem, but aren't 100% happy with any of my options. Currently I'm leaning toward something like the following where the emit() method of event emitter instances returns a promise:

// host library code
program()
  .initStuff()
  .then(function() {
    dispatcher.emit('beforeRenderPost')
  })
  .then(function() {
    dispatcher.emit('afterRenderPost')
  })
  .then(function() {
    dispatcher.emit('beforeWritePost')
  })
  .then(function() {
    dispatcher.emit('afterWritePost')
  })
  .catch(errorHandler)

// now the user's code can simply be the following and the host logic // will wait until their async function is finished before continuing dispatcher.on('beforeRenderPost', doSomethingAsyncToPost)

I like this idea of this, but I'm afraid to adds unnecessary confusion to the existing understand of events. Anyway, I'd be interested in your thoughts on the subject.

Oops, I guess markdown code blocks aren't supported. Here's a gist that may be easier to read.

@philwalton yes, Events are usually a one-way broadcast. It doesn't care about who is listening to the event, and what they are doing after they receive it. So if you need some sort of hook that should wait for async operations to complete when triggered you should probably create an special type of dispatcher that cares about the value returned by the listeners (if they return a promise you wait till they are resolved); similar to Q.all. - I would like to see something like that, could simplify logic in a few cases.

As promises have seen more and more light coverage across the blogosphere, I've been feeling their limitations more and more. The specification is pretty restrictive (as you say, there is only one resolution), and in many situations where they suggest themselves to me, I realise the chaining of asynchronous listeners is the crucial benefit.

I can't help thinking that something somewhere between jQuery's Deferred and Node's chained streams (both noteworthy — at least until jQuery dropped it in 1.8 — for the crucial pipe method) will make a big impact in the near future: something with the elegance of promises without their restrictions, and the freedom of events without their centralised flow and open nature.

John Resig recently wrote about Node streams in such a way that readily suggests front-end applications: http://ejohn.org/blog/node-js-stream-playground/ — when you consider WebSockets, rich user data input (say, interaction with a WebGL games), workers etc, these seem much more appealing than the alternatives.

On signals vs events, signals are more discoverable, but cannot bubble. As in, with events, you can listen for an event on an object, even if that object does not dispatch that kind of event.

Eg, you can listen for the "change" event on body, and hear about when form elements change that are descendants of body.

I'm not really clear on Signals... your example doesn't explain their use or how they differ from Events in any meaningful way... could you further explain Signals? I see the benefit in leveraging Promises, Evens, and Callbacks, but I want to see if Signals need to go into my toolbox as well.

Jon: Signals are discoverable, but cannot have a capture/bubble phase like events do.

obj.addEventListener("eventNaem", func); // silent failure
obj.signals.eventNaem.add(func); // Error: cannot call add on undefined

However:

document.body.addEventListener('change', func, true); // you can't do this with signals

About promises:

making error handling simpler in some cases

My personal experience is that they make error handling simpler in most cases, especially when you run on NodeJS. No uncaught exceptions that require server restart to make sure you have node in a sane state.

[…] Learn the difference between Callbacks, Promises, Signals and Events […]

orderPizza(toppings) .then(processStatus, showOrderFailure) .then(showOrderSuccess);

should be:

orderPizza(toppings) .then(processStatus) .then(showOrderSuccess, showOrderFailure);

because the second callback of a Promise is a recover (at least in Q), if recovered a Promise is successful again.

Another post with more in-depth info on signals would be helpful. I'm still not totally clear on them even after reading this post and the comments.

Helton Valentini

The big difference between events and signs in my vision, is that Signals are related to the application and Events are related to the elements / components (DOM). I would really like to see another article explaining how you distinguish between them on your coding.

Leave a Comment

Please post only comments that will add value to the content of this page. Please read the about page to understand the objective of this blog and the way I think about it. Thanks.

Comments are parsed as Markdown and you can use basic HTML tags (a, blockquote, cite, code, del, em, strong) but some code may be striped if not escaped (specially PHP and HTML tags that aren't on the list). Line and paragraph breaks automatic. Code blocks should be indented with 4 spaces.