Evented programming patterns: Asynchronous methods

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

Building on the pattern for deferred processing that we just saw, asynchronous methods are typically used to return a value from a method that may have to do some work in the background to compose its return value. In an async method call, we use continuation-passing style where instead of calling the method, getting its return value then doing something with it, we send the method a continuation that it will call when it has the information we need.

Again, the typical use case for this is Ajax on the client-side, though its use has recently expanded to cover many other forms of I/O through frameworks like Node.js and EventMachine. The important thing about an async method is that it frees the implementation up to be non-blocking, since the caller is not expecting a value immediately. Instead the method being called is responsible for resuming the caller’s processing when ready.

If you’ve done any client-side work you’ll probably have made an Ajax call or two:

jQuery.get("/hello.html", function(response) {
  console.log(response);
});
console.log("done");

Compare this to the usual blocking form of I/O used by most server-side environments:

require "open-uri"
response = open("http://example.com/hello.html").read
puts response
puts "done"

The first example will print "done" before it prints the response, while the second example will print the response first. This is because with blocking I/O, your program halts while your machine goes off and does some I/O to get your data. No other work can be done until the response comes back over the wire. With the asynchronous approach, the I/O can be done in the background while your program continues working, and when the response comes back, the part of the program that needed it can run. For many forms of I/O, and especially for network calls, this can yield much more responsive applications: if web browsers used blocking I/O for Ajax, your UI would lock up every time the browser made a network call.

Implementing an async method is very straightforward compared to the other patterns we’ve covered, as it does not involve any state or long-term storage of callbacks. It will usually be based on existing async APIs provided by the host environment, and you’ll typically use it to provide abstractions on top of these APIs or to simplify the interface to a Deferrable. For example the Node APIs recently changed such that many methods now take a continuation rather than returning a deferred object (a ‘Promise’ in Node’s terms).

For example, you may have a set of data stored in a JSON document, which may be on the local machine or may be accessible over the web. You want a function to get that data as JavaScript objects just by specifying the path. Since we can do async I/O, the function will not return the parsed data directly but instead will take a continuation that we will call with the JSON-parsed document when ready.

// Using Node.js
var fs   = require("fs"),
    url  = require("url"),
    http = require("http");

getMyData = function(path, continuation, scope) {
  var uri = url.parse(path);
  if (uri.hostname) {
    var client  = http.createClient(uri.port, uri.hostname),
        request = client.request("GET", uri.pathname);
    
    request.addListener("response", function(response) {
      response.addListener("data", function(data) {
        continuation.call(scope, JSON.parse(data));
      });
    });
    request.close();
    
  } else {
    fs.readFile(path, function(err, data) {
      continuation.call(scope, JSON.parse(data));
    });
  }
};

This function wraps a couple of Node’s event-based I/O APIs and just gives your continuation the result of JSON.parse(). As covered in previous articles, the optional scope argument sets the binding of this within the continuation. This could be used to implement backing storage for a data object:

BlogPost = new JS.Class({
  initialize: function(path) {
    getMyData(path, function(data) {
      this.title = data.title;
      // more attributes ...
    }, this);
  }
});

You could imagine combining this with a Deferrable to provide a way for callers to wait for the BlogPost’s data.

When should you reach for an async method? Well it’s basically a simplified version of the deferrable object; you’d use it where you need to hide some long-running background processing to return a value without blocking the rest of the program. It’s not as powerful as deferrables since you only get one callback per deferred value, but it has a simpler interface and requires less setup and explicit state, and for many async jobs it’s just fine. As with all evented code, you’ll need to keep in mind that your callback will fire at some not-entirely-predictable time in the future, and this makes it harder to reason about control flow and can lead to race conditions if you’re not careful about your application’s state. But for many applications, the gains in responsiveness are well worth extra discipline required.

Come back at the end of the week for a round-up of the techniques I’ve covered and what to watch out for when using them in production.