Asynchronous function chaining in JavaScript

Update, 25 February 2008: This class is now available as part of JS.Class (it’s called MethodChain now). It also forms a key part of Ojay, an expressive wrapper for YUI.

Update, 12 Dec 2007: Another implementation change. A blank ChainCollector instance now has the following properties: then, and, ____ (formerly __enqueue) and fire. The method queue and base object are private variables, and addMethods is a static method. I’m trying to make it as flexible as possible, i.e. it should have the fewest possible named properties but still allow you to extend it to your needs. Also, you now add methods to ChainCollector’s prototype rather than to instances as this is much faster.

Update, 9 Dec 2007: I’ve modified the implementation of ChainCollector so it has fewer methods in its prototype, thus fewer chances of name collisions. It no longer has initialize, __addMethods or __enqueue methods – these are now ‘private’ methods created using the module pattern.

For this post, I’m going to be using jQuery, because it’s the closest well-known thing to the library I’m currently working on at my new job. (It’s a wrapper for YUI with lots of syntax niceties.)

Programming asynchronous actions is a pain in the head. Why can’t I do setTimeout($('#myNode').hide, 2000)? I need to bind the hide function to the $('#myNode') object for starters, and jQuery doesn’t give you a bind method, and besides, binding and execution scope gives lots of JavaScript novices a headache. Wouldn’t this be nice:

$('#myNode').wait(2).then.hide();

Over the weekend, I was trying to figure out a general purpose way of adding asynchronous behaviour like this, so I could use it with event handlers, Ajax calls, post-animation callbacks etc. My inspiration came from Methodphitamine (site unavailable, try the Google cache).

I won’t bore any non-Rubyists with the details, but the idea is quite simple: create an object with no predefined methods, which accepts any method call and adds the name of the method and its arguments to a queue. This queue can be turned into a function and called on whatever object you want at a later time. Unfortunately, JavaScript has no analogue for Ruby’s method_missing, which means if you want such an object in JavaScript, you need to predefine every method name you might want to use. Big pain.

But, you can get something quite usable if you’re implementing an interface (like jQuery’s) that allows chaining. You can pass some object to the constructor for your magical queue-collecting object, and have it implement copies of all the object’s methods. If you need methods from other objects, you can pass those in too.

An example, implementing my wait suggestion from above:

jQuery.fn.wait = function(time) {
  var collector = new ChainCollector(), self = this;
  // Deal with scoping issues...
  var fire = function() { collector.fire(self); };
  setTimeout(fire, Number(time) * 1000);
  return collector;
};

// Then extend ChainCollector with all jQuery's methods
ChainCollector.addMethods(jQuery);

With this defined (and ChainCollector, code to follow), you can do this:

$('#jq-header').wait(2).then.hide('slow')

Grab all the code from this post and go try that out on the jQuery home page. Nice, no? I’m using this technique for writing concise, extensible event handlers at the moment, and it’s very nice indeed. You can even chain multiple wait calls into the same statement:

$('#jq-header').wait(2).then.hide('slow').wait(3).then.show('fast')

Anyway, now that I’ve got you all fired up about this, here’s the magic that makes it all possible:

var ChainCollector = function(base) {
    var CLASS = arguments.callee;
    
    this.then = this.and = this;
    var queue = [], baseObject = base || {};
    
    this.____ = function(method, args) {
        queue.push({func: method, args: args});
    };
    
    this.fire = function(base) {
        var object = base || baseObject, method, property;
        for (var i = 0, n = queue.length; i < n; i++) {
            method = queue[i];
            if (object instanceof CLASS) {
                object.____(method.func, method.args);
                continue;
            }
            property = object[method.func];
            object = (typeof property == 'function')
                    ? property.apply(object, method.args)
                    : property;
        }
        return object;
    };
};

ChainCollector.addMethods = function(object) {
    var methods = [], property, i, n, name;
    var self = this.prototype;
    
    var reservedNames = [], blank = new this();
    for (property in blank) reservedNames.push(property);
    var re = new RegExp('^(?:' + reservedNames.join('|') + ')$');
    
    for (property in object) {
        if (Number(property) != property)
            methods.push(property);
    }
    if (object instanceof Array) {
        for (i = 0, n = object.length; i < n; i++) {
            if (typeof object[i] == 'string')
                methods.push(object[i]);
        }
    }
    for (i = 0, n = methods.length ; i < n; i++)
        (function(name) {
            if (re.test(name)) return;
            self[name] = function() {
                this.____(name, arguments);
                return this;
            };
        })(methods[i]);
    
    if (object.prototype)
        this.addMethods(object.prototype);
};

I’d love to know what uses people find for this.