The deferred object is very useful when deal with asynchronous event in Javscript like load js or send ajax request.

In a distributed environment like Web application, the network delay is unavoidable and uncontrollable, many things just can not finish immediately, but suspend the application is also unacceptable for users.

The simplest solution is using the callback, this already be used for long time.

For advanced using, we need better solutions, one of them is deferred object.

New jQuery release has begin to use Deferred object to replace old implementation. For example, the $.ajax now return a deferred object.

done , fail , reject , resolve and promise

A deferred object allows you register callback functions with method like done of fail, the callbacks will be added to respondent queues. They will be triggered according to the state of the asynchronous event in the future.

For the user of the deferred object, only the register methods shall be exposed, not the trigger method. This is done by a promise object, the promise object can register new callback functions but can not trigger the deferred object. The trigger also called resolve or reject.

Implements a simple deferred object

Now we will implements a simple deferred object to see how it works.

 
var APP = window.APP || {};
 
    (function () {
 
        APP.Deferred = function() {
            var deferredState = "pending";
            var callbackQueues = {
                done: [],
                fail: [],
                always: []
            };
            var args = [];
 
            function registerOrTrigger(action, callback) {
                if (typeof callback === "function") {
                    if (deferredState === action || (action === "always" && deferredState !== "pending")) {
                        setTimeout(function() {
                            callback.apply({}, args)
                        }, 0)
                    } else {
                        callbackQueues[action].push(callback)
                    }
                } else {
                    if (deferredState === "pending") {
                        deferredState = action;
                        var requesteQueue = callbackQueues[action];
                        var alwaysQueue = callbackQueues.always;
                        while ((callback = requesteQueue.shift()) || (callback = alwaysQueue.shift())) {
                            setTimeout((function(k) {
                                return function() {
                                    k.apply({}, args)
                                }
                            })(callback), 0)
                        }
                    }
                }
            }
            return {
                state: function() {
                    return deferredState
                },
 
                done: function(callback) {
                    if (typeof callback === "function") {
                        registerOrTrigger("done", callback)
                    } else {
                        args = [].slice.call(arguments);
                        registerOrTrigger("done")
                    }
                    return this
                },
                fail: function(callback) {
                    if (typeof callback === "function") {
                        registerOrTrigger("fail", callback)
                    } else {
                        args = [].slice.call(arguments);
                        registerOrTrigger("fail")
                    }
                    return this
                },
 
                always: function(callback) {
                    if (typeof callback === "function") {
                        registerOrTrigger("always", callback)
                    }
                    return this
                },
                promise: function() {
                    return {
                        done: function(callback) {
                            if (typeof callback === "function") {
                                registerOrTrigger("done", callback)
                            }
                            return this
                        },
                        fail: function(callback) {
                            if (typeof callback === "function") {
                                registerOrTrigger("fail", callback)
                            }
                            return this
                        },
                        always: function(callback) {
                            if (typeof callback === "function") {
                                registerOrTrigger("always", callback)
                            }
                            return this
                        },
                        state: function() {
                            return deferredState
                        }
                    }
                }
            }
        };
 
        APP.when = function() {
            var deferred = APP.Deferred(),
                args = [].slice.call(arguments), 
                argLen = args.length,
                count = 0;
            if (!argLen) {
                return deferred.done().promise()
            }
            for (var e = args.length - 1; e >= 0; e--) {
 
                args[e].fail(function() {
                    deferred.fail()
                }).done(function() {
                    if (++count === argLen) {
                        deferred.done()
                    }
                })
            }
            return deferred.promise()
        }
    })();
 

The deferred object contains callback queues, you can add functions to those queues, when the asynchronous event fail or done, the corespondent queues will be executed.

The when function will accept an array of deferred objects, with the returned promise object you can specify the functions that will be executed when all the deferred objects failed or done.

This is useful when one Javascript file depend one or more other Javascript files. For each Javascript file, we associate a deferred object. When the Javascript is loaded and ready for using, its deferred object is resolved or done. When all the dependencies are done, the current Javascript can do some initial work.

How to use it

Here are some examples of using this tool.

 
var deferred = new APP.Deferred();
deferred.done(function() {
    console.log("we are done");
});
 
var deferred2 = new APP.Deferred();
deferred.done(function() {
    console.log("we are done");
});
 
var dependList = [];
dependList.push(deferred);
dependList.push(deferred2);
 
APP.when.apply({}, dependList).done(function() {
   console.log("all done");
});
 
 
jQuery.ready(function() {
    deferred.done(); 
    deferred2.done(); 
});
 

Output

 
we are done
we are done
all done