Evented programming patterns: Deferrable values

This post is part of a series on event-driven programming. The complete series is:

The Deferrable pattern is a specialisation of observable objects that mixes state into the event dispatch process. The goal is to encapsulate a long-running computation as an object that can have callbacks attached to it; each callback is called just once, as soon as the computation’s result is known. The classic client-side JavaScript example is Ajax web services: we need a piece of data from an API but we might have to go over the wire to get it. To hide these details, we may use a JavaScript API that returns a deferrable object. Multiple clients can call the API and get such an object, and all will be notified when the data is ready.

Let’s take an example. Suppose we have an API that exposes data about pictures on a photo-sharing website as JSON. A JavaScript object wraps the HTTP API and memoizes the responses. Again I’m using JS.Class and Ojay for my examples.

PhotoService = new JS.Class({
  initialize: function(host) {
    this._host = host;
    this._photos = {};
  },
  
  getPhoto: function(id) {
    if (this._photos[id]) return this._photos[id];
    var photo = new Photo();
    Ojay.HTTP.GET(this._host + '/photos', {id: id}, function(data) {
      photo.succeed(data);
    });
    this._photos[id] = photo;
    return photo;
  }
});

Our API returns a Photo object, but one whose content is initially not known. (Such an object is also known as a ‘future’ or a ‘promise’ in some contexts.) If we’ve already made a Photo with the required id we return it so as to avoid unnecessary network calls. Otherwise we make a new one, and begin an asynchronous network call to get its contents. It is not known how long the call will take but it is certainly enough to stop the method from returning anything useful immediately. So, the Photo we return is really a placeholder for a value we might know at some point in the future. A client using this API would behave like so:

var service = new PhotoService('http://example.com');
var photo = service.getPhoto('f80046');

photo.callback(function(photoData) {
  var username = photoData.user.username,
      photoUrl = photoData.url;
  
  // further processing...
});

What must the Photo look like? It must be able to have event listeners attached using callback() that wait for its value, and it must have a way of being populated with data using succeed() so that clients get the data they’re waiting for. It turns out that these are fairly generic operations and we can implement them as a module to mix into our Photo class.

Deferrable = {
  callback: function(block, scope) {
    if (this._deferredValue !== undefined)
      return block.call(scope, this._deferredValue);
    
    this._callbacks = this._callbacks || [];
    this._callbacks.push([block, scope]);
  },
  
  succeed: function(value) {
    this._deferredValue = value;
    if (!this._callbacks) return;
    this._callbacks.forEach(function(callback) {
      callback[0].call(callback[1], value);
    });
    this._callbacks = [];
  }
};

Photo = new JS.Class({
  include: Deferrable
});

This code bears some similarity to our observable object examples in that it’s just storing and calling callback functions, but there are some important differences. The callback() method checks to see if we already know the object’s value, and if we do then it calls the callback immediately and promptly forgets about it. Otherwise the callback is stored in a queue until the value is known. succeed() is what we use to give the object a value: the value is cached on the object and if any callbacks have been registered they are fired then removed from the object. This makes sure each piece of code waiting on the object’s value executes just once.

When should you use a deferrable object? The above example, though a little contrived, is the typical client-side use case but really you can use them in any situation where you need to wait for some background/parallel processing to finish. This could be an Ajax call, an animation, a complex calculation happening in another thread, etc. One case I’ve been using it for lately is where an object needs to make sure some condition is true before executing some code.

The particular example I have in mind is the Client class in Faye, a publish-subscribe messaging library for web clients. You’ll notice several of the methods, for example publish(), have their internals wrapped in a callback passed to the connect() method. This is saying, “Making sure the client is connected, run this code.” I’ve used the Deferrable pattern to implement this but it’s probably obfuscated a little by the rest of the logic, so I’ll make it more explicit here.

In Faye, each Client must connect to the server and get a clientId before it’s allowed to subscribe and publish messages. A client can therefore exist in one of three states:

  • UNCONNECTED. The client has no clientId and is not currently attempting to connect.
  • CONNECTING. The client is currently making a request for a clientId.
  • CONNECTED. The client has a clientId and may publish messages.

Therefore our connect() method must handle these three states. In the first state, we register a callback and initiate a request, and when the request returns we use succeed() to fire callbacks waiting for the Client to be ready. In the second state, we just register a callback since the request should already be in progress. In the third, we can execute the callback immediately since the Client is ready.

Client = new JS.Class({
  include: Deferrable,
  
  UNCONNECTED:  1,
  CONNECTING:   2,
  CONNECTED:    3,
  
  initialize: function() {
    this._state = this.UNCONNECTED;
  },
  
  connect: function(block, scope) {
    switch (this._state) {
      
      case this.UNCONNECTED:
        this.callback(block, scope);
        this._state = this.CONNECTING;
        Ajax.getClientIdSomehow(function(clientId) {
          this._clientId = clientId;
          this._state = this.CONNECTED;
          this.succeed(true);
        }, this);
        break;
      
      case this.CONNECTING:
        this.callback(block, scope);
        break;
      
      case this.CONNECTED:
        block.call(scope);
        break;
    }
  },
  
  publish: function(channel, message) {
    this.connect(function() {
      // publishing logic
    }, this);
  }
});

Most uses of deferrables fall into this three-state model: first we’re not ready, then we’re doing some background work to get a value, then we’re ready to use the value. They can be very useful as long as you’re careful to manage state correctly: make sure you don’t leave callbacks hanging around after they should have been fired and cleaned up, and make sure you do the right thing in each of the three states. In particular, be careful not to use a deferrable when what you really want are stateless multi-fire events (as in the previous article) or an asynchronous method – I’ll be covering those next.