Thursday, May 22, 2014

Chaining JavaScript callbacks

Ever load an XML file in JavaScript? Or done any type of asynchronous load, such as a call to an API which returns JSON? 'Course you have! It's what we spend much of our precious time doing in JavaScript, right? And more than likely you'll have used the trusty $.ajax() jQuery method, or at least one of its derivatives, like $.get() or $.load(). And in that case, you'll know about chaining callbacks.


Callbacks can be a bit tricky: I've known good devs who struggled with some or other aspects of callbacks, as I have myself, which is why I'm writing about it - to help make sense of callbacks. Anything that takes a concrete thing, like a function, and places it one level away from you on the abstract stack can be hard to deal with if you're not used to it. As a predominantly C# dev, function callbacks have traditionally been as rare as hen's teeth, although that's all changed in the last few years.

The World Toilet Organization gets in line for the World's Longest Toilet Queue
Flickr The World Toilet Organization gets in line for the World's Longest Toilet Queue

Anyway I've found myself having to write a few callbacks recently in JavaScript, and thought it would be useful to describe the different types here. Callbacks are often used in conjunction with asynchronous operations, and that's what I'm using in my example here. I have a client script, a JavaScript file such as you might have on a web page that needs to load an XML data source. The code to load the XML is in a different file, one containing common news functions. Here it is:
var news = {
    parseXML: function(callback, amount,xml) {
        // do some parsing
        var latestStories = xml.GetTopStories(amount);

        // then execute the callback
        callback(latestStories);
    }

    LoadXML: function (callback) {
        $.ajax({
            url: "/news.xml"
        }).done(function (data) {
            callback(data);
        }).fail(function() {
            alert("Couldn't get the news!");
        });
    }
};
Note that the callback that happens after the news XML has been successfully loaded in LoadXML() is pretty spartan. Reading this code all you can really say is that when the news XML file has been loaded something should happen. I'm not making any assumptions about what should be done here, even though it may well be that all the files that end up using LoadXML() do similar things once the XML has loaded. Far better to have a bit of repetition than to get ahead of yourself and assume that every client that consumes this method will want to allow the done() method to dictate what happens, even to a small extent. Anyway, from the calling code's point of view, to get started we could try this as a naïve way to get a notification that the news XML has loaded:
var client = {
    news.LoadXML(alert("news loaded!");
}
Alas, the alert() method here is not a callback. This code is simply going to invoke the alert method, then the LoadXML method, synchronously. One straight after the other with no pause. Whereupon the LoadXML() method will be nonplussed to receive a callback parameter which is undefined, and naturally nothing will happen after the XML loads. To package our method up as a callback, we have to wrap it up in a function:
var client = {
    news.LoadXML(function(data) {
        alert("news loaded!");
    });
}
This is the simplest type of callback, one where we don't even use the data returned. This may seem like a pointless example, and it is, but I've seen code like this in the wild. Anyway, this example is simply to show that the callback - the alert() method - is just a function which doesn't get executed until the XML has finished loading. In jQuery the .done() method is called a "callback hook", a place in which you can execute another method safe in the knowledge that it will only happen after a previous event has already occurred, in this case the successful loading of the XML data. Here's a more typical example:
var client = {
    ShowStories: function(amountOfStoriesToShow, data) {
        // parse the XML and only showing 2 stories
    }

    news.LoadXML(function(data) {
        client.ShowStories(2, data);
    }
}
ShowStories() is a method within the same client script that calls LoadXML in the first place, and I want it to execute after the news data has loaded. Note that I can't just call it like this: this.ShowStories(2, data) because in the context of the .done() method in the news script, "this" won't be a reference to the client script anymore.

So the flow is: client script calls the news script, which, when it's good and ready and not a millisecond before, calls a method back in the client script. In terms of the formal quality of the callback, you can see the way the .done() method and our LoadXML() method resemble each other in that they both take a function with one parameter, data:
.done(function (data) {
    callback(data);
})

news.LoadXML(function(data) {
    alert("news loaded!");
})

Building a nest

Pretty simple, like forgetting the capital of Qatar*. But that's only because I have only one line's worth of callback business there. Any more, and the similarity breaks down, leaving only the
(function (data) {
part reliably the same, so it really helps if you keep the "data" variable name the same.

So like I say, it's still rather a simple example: we're only only going down one level. The function we pass in to the AJAX method on the news page is just an anonymous function calling a single function back in our calling code. What happens if we want the callback to do something else in the common news file, say parse the XML a bit, before returning it? Our callback needs a callback.
news.LoadXML(function (xml) {
    news.parseXML(client.ShowStories, 2, xml);
});
We just wrap it in a call to parseXML() in the news script, passing in the parameters that that method will need, including of course the original method it needs to return to the client script to execute. The news script gets the XML, send the data to parseXML(), along with the method to return to on the client once that intervening method has run and stripped out only the latest (2) stories.

A very plain example

In fact, I've added two arbitrary extra methods to our chain just to emphasise how it's done, and simplifying everything to a vanilla level, we have:
var news = {
    Method1: function (method2, method3, method4, xml, arbitraryKey) {

        // do something

        method2(method3, method4, xml, arbitraryKey); // pass it on!
    },

    method2: function (method3, method4, xml, arbitraryKey) {

        // do something to the XML with the arbitrary key
        var updatedXML = XML.DoSomething(arbitraryKey);

        method3(method4, xml); // pass it on again (no need for arbitraryKey now)!
    },

    method3: function (method4, xml) {

        // do something

        method4(xml); // send 'er home!
    },

    LoadXML: function (callback) {
        $.ajax({
            url: "/news/stories.xml"
        }).done(function (data) {
            callback(data);
        }).fail(function() {
            alert("Couldn't get the news!");
        });
    }
};

news.LoadXML(function (xml) {
    var arbitraryKey = "myKey";
    news.Method1(news.Method2, news.Method3, homePage.FinalMethod, xml, arbitraryKey);
});
In the calling code, the first method to be executed once the data is loaded wraps a list of method parameters, arranged in the order in which they will be executed, culminating in the one back on the client, and of course the xml which it needs in the first place. I have also included a cunningly-titled arbitrary variable to show how that needs to be passed through by any middleman methods until it is used by Method2() at which point it need go no further along the chain of methods. The calling code is easy to read, being a chronologically-ordered list of methods which get executed, one after the other, upon successful loading of the data upon which they all depend - the XML data.

* It's...Oh crap!