Chaining promises by Will Acton / 10 May 2015

At work we still use jQuery for a lot of our front end development. It works well enough for most of our needs (mostly simple DOM manipulation and AJAX), and has only started to become cumbersome now that we are migrating towards a more SPA experience for some of our features.

Because of the increasing complexity of our applications, loading multiple Handlebars templates simultaneously has become necessary.

At first pass, our code looked something like this:

// getView is a wrapper for the jQuery AJAX call and
// returns a promise
getView('interface').done(function (template) {  
    getView('details').done(function (detailsTemplate) {
    // ...
    });
});

This is obviously terrible for more than one or two views, so it was quickly refactored using jQuery's $.when function:

$.when(getView('interface'), getView('details'), ...)
    .done(function (interfaceTemplate, detailsTemplate) {
        // Compile the views and do stuff
    });

However this quickly became cumbersome as well, because anytime we wanted to add a new view a new getView() call had to be added to the $.when statement, then make sure that the callback function arguments were in the correct order, and then compile all of the views.

The solution I settled on was to use jQuery's Deferred API so that once the $.when resolved, I could bundle up our templates and return it in one nice object for us to use.

It eventually looked something like this:

var getViews = function(viewIDs) {  
    $d = $.Deferred(); // [1]
    var promiseArray = viewIDs.map(function (id) {
        return getView(id);
    }); // [2]

    $.when.apply(null, promiseArray).done(function () { // [3]
        var cache = {};
        arguments.forEach(function (view, i) {
            cache[viewIDs[i]] = view[0]; // get the string payload
        }); // [4]

        $d.resolve(cache); // [5]
    });

    return $d.Promise();
};

What the code does is:
1. Create a jQuery.Deferred object for us to return to the caller.
2. Create an array of promises returned by the getView() function
3. Use the array of promises as arguments to $.when, so we complete our task only once they all resolve.
4. Once all the getView() calls resolve, create an object with the views. e.g. { 'interface' : <Handlebars> }
5. Resolve our promise we're returning with the object containing all of our views sent as an argument.

By creating our own promise (rather than using the promise returned by $.when), it allows us to do some post-processing after the AJAX requests are resolved and then send the data to a callback all nicely packaged in a single object, instead of having to deal with each template by name, in order. This function can now stretch from one view to hundreds of views without a single line of code change.

Now in our applications, our view retrieval looks like this:

getViews(['interface', 'details', ...])  
    .done(function (views) {
        // compile views and do stuff
    });

A much cleaner, more robust implementation leveraging jQuery's deferred object and chaining promises. Enjoy!