Eli Grey

Pausing JavaScript with async.js

async.js is a library that aims to make it so you don’t have to mess with callbacks when making applications in JavaScript 1.7 or higher by using the yield statement to pause function execution.

Examples

Please note that user interaction with the page is not blocked during the course of any of these examples.

node.next(eventType) method

The node.next(eventType) method would pause a function until the specified event is fired on the node that next was called on and would return the captured event object.

var listenForNextEventDispatch = function ([node, eventType], callback) {
    var listener = function (event) {
        node.removeEventListener(eventType, listener, false);
        callback(event);
    };
    node.addEventListener(eventType, listener, false);
};
Node.prototype.next = function (eventType) {
    return [listenForNextEventDispatch, [this, eventType]];
};

You could now do the following in an async()ed function to handle the next click event on the document.

var clickEvent = yield document.next("click");
// handle click event here

Asking the user for their impressions of async.js

The following code does not use any obtrusive and annoying functions like prompt or alert yet still can utilize execution-blocking features.

yield to.request("feedback", "POST", (
    yield to.prompt("What are your impressions of async.js?")
));
yield to.inform("Thanks for your feedback!");
// do more stuff here

As opposed to the following, which is functionally equivalent to the previous code but doesn’t use async.js’s blocking features.

async.prompt(
    ["What are your impressions of async.js?"],
    function (response) {
        async.request(
            ["feedback", "POST", response],
            function () {
                async.inform(
                    ["Thanks for your feedback!"],
                    function () {
                        // do more stuff here
                    }
                );
            }
        );
    }
);

That’s a lot of callbacks, all of which are implied when you use async.js.

Creating an async.js module for thatFunctionThatUsesCallbacks

async.yourMethodName = function ([aParameterThatFunctionUses], callback) {
    thatFunctionThatUsesCallbacks(aParameterThatFunctionUses, callback);
};

You could then use yield to.yourMethodName(aParameterThatFunctionUses) and immediately start writing code that depends onthatFunctionThatUsesCallbacks function after the statement.

2 Comments (add yours)

  • Interesting stuff, as I've recently been set onto the path of NarrativeJS and Strands in order to save the same sort of problems. They seemed wonderful at first, but I encountered a handful of bugs with both, and debugging was a nightmare.

    Having said that, I must admit that it took me a good day or so to actually wrap my head around what's actually going on here. I'm still not sure if I'm doing things oddly, but I thought I'd share the function call descriptor generator that I find most natural:

    Function.prototype.result = function() { 
        var self=this; 
        var wrapper = function(func_args, cb) { 
            func_args = Array.prototype.slice.call(func_args); 
            func_args = func_args.slice(); 
            func_args.push(cb); 
            async(self).apply(this, func_args); 
        } 
        return [wrapper, arguments]; 
    }

    this allows me to just have all my functions be regular (non-async-aware) functions that expect one more argument than they're called with (the callback argument). Sample usage:

    function add_one(arg, cb) { 
        cb(arg + 1); 
    } 
     
    function main() { 
        alert("1 + 1 = " + (yield add_one.result(1))); 
    } 
     
    async(main)();

    The to function-call-description generator does feel less awkward to write with, but it seems to require functions be available in the global scope, which wouldn't work for me. And this way, I don't have to write wrapper functions for everything that just uses a callback without being async-aware. it also wrapps everything in as async() call as it goes, so that it should correctly deal with a yield wherever in the stack it occurs.

    • I like your idea, and will include something similar to it in async.js. Instead of extending Function’s prototype, I’m going to make the to object a function that takes a function as an argument and returns a function call descriptor generator for it that assumes the last argument of the function passed to it is the callback.

Leave a Reply