2017年2月14日 星期二

(js)Deferred

/* jq1.6.2 */


var __id = 0; // for test
var // Promise methods
    promiseMethods = "done fail isResolved isRejected promise then always pipe".split(" "),
    // Static reference to slice
    sliceDeferred = [].slice;

jQuery.extend({
    // Create a simple deferred (one callbacks list)
    _Deferred: function() {
        debugger;

        var callbacks = []; // callbacks list
        var fired; // stored [ context , args ]

        var firing; // to avoid firing when already doing so

        var cancelled; // flag to know if the deferred has been cancelled

        // the deferred itself
        var deferred = {

            // done( f1, f2, ...)
            done: function() {
                debugger;

                if (!cancelled) {
                    var args = arguments,
                        i,
                        length,
                        elem,
                        type,
                        _fired;
                    if (fired) {
                        _fired = fired;
                        fired = 0;
                    }
                    for (i = 0, length = args.length; i < length; i++) {
                        elem = args[i];
                        type = jQuery.type(elem);
                        if (type === "array") {
                            deferred.done.apply(deferred, elem);
                        } else if (type === "function") {
                            callbacks.push(elem);
                        }
                    }
                    if (_fired) {
                        deferred.resolveWith(_fired[0], _fired[1]);
                    }
                }
                return this;
            },

            // resolve with given context and args
            resolveWith: function(context, args) {
                debugger;

                if (!cancelled && !fired && !firing) {
                    // make sure args are available (#8421)
                    args = args || [];
                    firing = 1;
                    try {
                        while (callbacks[0]) {
                            callbacks.shift().apply(context, args);
                        }
                    } finally {
                        fired = [context, args];
                        firing = 0;
                    }
                }
                return this;
            },

            // resolve with this as context and given arguments
            resolve: function() {
                debugger;

                deferred.resolveWith(this, arguments);
                return this;
            },

            // Has this deferred been resolved?
            isResolved: function() {
                return !!(firing || fired);
            },

            // Cancel
            cancel: function() {
                debugger;

                cancelled = 1;
                callbacks = [];
                return this;
            }
        };
        deferred.id = __id++; // for test

        return deferred;
    },

    // Full fledged deferred (two callbacks list)
    Deferred: function(func) {
        debugger;

        var deferred = jQuery._Deferred();
        var failDeferred = jQuery._Deferred();
        var promise;



        // Add errorDeferred methods, then and promise
        jQuery.extend(deferred, {
            then: function(doneCallbacks, failCallbacks) {
                debugger;

                deferred.done(doneCallbacks).fail(failCallbacks);

                return this;
            },
            always: function() {
                return deferred.done.apply(deferred, arguments).fail.apply(this, arguments);
            },
            fail: failDeferred.done, // here is important(把 deferred 導向 failDeferred 的閉包)
            rejectWith: failDeferred.resolveWith,
            reject: failDeferred.resolve,
            isRejected: failDeferred.isResolved,
            pipe: function(fnDone, fnFail) {
                return jQuery.Deferred(function(newDefer) {
                    jQuery.each({
                        done: [fnDone, "resolve"],
                        fail: [fnFail, "reject"]
                    }, function(handler, data) {
                        var fn = data[0],
                            action = data[1],
                            returned;
                        if (jQuery.isFunction(fn)) {
                            deferred[handler](function() {
                                returned = fn.apply(this, arguments);
                                if (returned && jQuery.isFunction(returned.promise)) {
                                    returned.promise().then(newDefer.resolve, newDefer.reject);
                                } else {
                                    newDefer[action](returned);
                                }
                            });
                        } else {
                            deferred[handler](newDefer[action]);
                        }
                    });
                }).promise();
            },
            // Get a promise for this deferred
            // If obj is provided, the promise aspect is added to the object
            promise: function(obj) {
                debugger;

                if (obj == null) {
                    if (promise) {
                        return promise;
                    }
                    promise = obj = {};
                }
                var i = promiseMethods.length;
                while (i--) {
                    obj[promiseMethods[i]] = deferred[promiseMethods[i]];
                }
                return obj;
            }
        });

        debugger;

        // Make sure only one callback list will be used
        deferred.done(failDeferred.cancel); // 執行環境的閉包屬 deferred
        deferred.fail(deferred.cancel); // 執行環境的閉包屬 failDeferred

        // Unexpose cancel
        delete deferred.cancel;

        // Call given func if any
        if (func) {
            func.call(deferred, deferred);
        }
        return deferred;
    },

    // Deferred helper
    when: function(firstParam) {
        debugger;

        var args = arguments,
            i = 0,
            length = args.length,
            count = length,
            deferred = length <= 1 && firstParam && jQuery.isFunction(firstParam.promise) ?
            firstParam :
            jQuery.Deferred();

        function resolveFunc(i) {
            debugger;

            return function(value) {
                debugger;

                args[i] = arguments.length > 1 ? sliceDeferred.call(arguments, 0) : value;
                if (!(--count)) {
                    // Strange bug in FF4:
                    // Values changed onto the arguments object sometimes end up as undefined values
                    // outside the $.when method. Cloning the object into a fresh array solves the issue
                    deferred.resolveWith(deferred, sliceDeferred.call(args, 0));
                }
            };
        }
        if (length > 1) {
            for (; i < length; i++) {
                if (args[i] && jQuery.isFunction(args[i].promise)) {
                    args[i].promise().then(resolveFunc(i), deferred.reject);
                } else {
                    --count;
                }
            }
            if (!count) {
                deferred.resolveWith(deferred, args);
            }
        } else if (deferred !== firstParam) {
            deferred.resolveWith(deferred, length ? [firstParam] : []);
        }
        return deferred.promise();
    }
});