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.







11 Responses to “Asynchronous function chaining in JavaScript”

You might also be interested in the AJAX Queue plugin for 1.2: http://dev.jquery.com/view/trunk/plugins/ajaxQueue/jquery.ajaxQueue.js

This allows you to queue up Ajax requests, and it won’t fire any until the previous one has finished - could be handy with your plugin

Tane Piper added these pithy words on Oct 30 07 at 5:11 pm

for that reason, in mootools we have Custom Event Listeners.
$(”hideme”).effect(”opacity”).start(0.7).addEvent(”onComplete”, function(){this.start(1)});

(or something like that);

nir added these pithy words on Nov 01 07 at 6:50 am

Prototype 1.6 (currently at RC1, with final release very soon) has added Function.prototype.delay. With it you can do stuff like $(’myNode’).hide.delay(2).

You should be able to look at the Prototype source and port the function over.

Andrew Dupont added these pithy words on Nov 01 07 at 5:21 pm

[...] The If Works · Asynchronous function chaining in JavaScript (tags: javascript jquery async programming tutorial asynchronous ajax oop chain chainability development web webdev) [...]

napyfab:blog» Blog Archive » links for 2007-11-01 added these pithy words on Nov 01 07 at 11:36 pm

This looks very interesting indeed. Would it be possible to use this for queuing Ajax calls when not using a library like jQuery? If so, any pointers on how to get started with that would be greatly appreciated.

Saq added these pithy words on Nov 10 07 at 10:18 pm

@Saq: this is not something that’s tied to jQuery — jQuery was just there to give me a well-known example in which to build something useful quickly. I’ll definately put together an article on your suggestion soon, as I’d rather not leave it totally abstract like this without showing the possibilities for real-world use.

James added these pithy words on Nov 13 07 at 11:41 am

Terrific, I’ll be looking forward to it James. I’ve been trying different ways of doing this myself (queue ajax calls), but own implementation was rather primitive and probably not very stable.

It basically went along the lines of pushing all the functions that need to be run in a queue into an array. Then having the callback for each Ajax call shift() the first member of the array and call it…..

It worked in my particular use case, but it’s far too ‘hacky’ for my own tastes even!

Thanks again!

Saq added these pithy words on Nov 13 07 at 11:45 am

I’m really looking forward to looking over your solulution and demos, though some of it may go way over my head… I came across your article when looking for a way (trying it with jquery) to allow users to, when hovering over a button, create/reveal a division (lets call it div.preview) placed close by and containing remotely loaded content; that div.preview would then remain visible for a set amount of time (eg. 1second), after which it would disappear unless the user moved the mouse over div.preview — doing so would reset the timer back up to 1sec, with the countdown starting only after the mouse exits the div#preview. Obviously, fun stuff like fadeIn and fadeOut can be added later…

you mentioned looking for a real-world use for the demo; howz this to try on? i’m trying to create that same effect for different buttons, with different contents loaded into their respective div.previews…

anyway, even this isn’t what ur looking for, thanks for the starting point…

Javed added these pithy words on Nov 26 07 at 5:51 am

This plugin is awesome. It makes doing nice looking status messages possible without having to do busy waiting. Just something along the lines of:

$(’#status’).fadeIn(’slow’).wait(5).fadeOut(’slow’);

All without having to do complicated things using animate (I suppose I could just give animate an empty transition for a certain amount of time, but this is much more succinct and general). I haven’t used it for anything else yet, but I imagine that there are a lot of possibilities.

Kyle added these pithy words on Dec 26 07 at 4:23 am

Very, very handy plugin .. but I can’t get it to work on IE browsers (6+7).

the following works on all browsers, except IE:
$(”#header”).wait(3).css(”visibility”, “visible”);

Hope you can give me a fix or workaround.

Thanks for the plugin anyway.

Bob added these pithy words on Jan 28 08 at 2:46 pm

Sorry, found out the problem lies elsewhere in my script .. not in your plugin.

Excuse me for wasting your time.

Bob added these pithy words on Jan 28 08 at 3:31 pm

Leave a Reply