What are deferred objects?

jQuery 1.5 adds "Deferred Objects". What are they, and what exactly do they do?


Deferred Object

As of jQuery 1.5, the Deferred object provides a way to register multiple callbacks into self-managed callback queues, invoke callback queues as appropriate, and relay the success or failure state of any synchronous or asynchronous function.

Deferred Methods:

  • deferred.done()
  • Add handlers to be called when the Deferred object is resolved.
  • deferred.fail()
  • Add handlers to be called when the Deferred object is rejected.
  • deferred.isRejected()
  • Determine whether a Deferred object has been rejected.
  • deferred.isResolved()
  • Determine whether a Deferred object has been resolved.
  • deferred.reject()
  • Reject a Deferred object and call any failCallbacks with the given args.
  • deferred.rejectWith()
  • Reject a Deferred object and call any failCallbacks with the given context and args.
  • deferred.resolve()
  • Resolve a Deferred object and call any doneCallbacks with the given args.
  • deferred.resolveWith()
  • Resolve a Deferred object and call any doneCallbacks with the given context and args.
  • deferred.then()
  • Add handlers to be called when the Deferred object is resolved or rejected.
  • Deferred In Action:

    $.get("test.php").done(
        function(){ alert("$.get succeeded"); }
    );
    
    $.get("test.php")
        .done(function(){ alert("$.get succeeded"); })
        .fail(function(){ alert("$.get failed!"); });
    

    And it seems that the existing ajax() method callbacks can be chained rather than declared in the settings:

    var jqxhr = $.ajax({ url: "example.php" })
        .success(function() { alert("success"); })
        .error(function() { alert("error"); })
        .complete(function() { alert("complete"); });
    

    Working Example From Eric Hynds blog post: http://jsfiddle.net/ehynds/Mrqf8/


    jqXHR

    As of jQuery 1.5, the $.ajax() method returns the jXHR object, which is a superset of the XMLHTTPRequest object. For more information, see thejXHR section of the $.ajax entry


    From JQUERY 1.5 RELEASED:

    DEFERRED OBJECTS

    Along with the rewrite of the Ajax module a new feature was introduced which was also made publicly available: Deferred Objects. This API allows you to work with return values that may not be immediately present (such as the return result from an asynchronous Ajax request). Additionally it gives you the ability to attach multiple event handlers (something that wasn't previously possible in the Ajax API).

    Additionally you can make your own deferred objects using the exposed jQuery.Deferred. More information about this API can be found in the Deferred Object documentation.

    Eric Hynds has written up a good tutorial on Using Deferreds in jQuery 1.5.


    Rather then telling you what it does, I'll show you what it does and explain it.

    A copy of the related source of jQuery 1.5 with annotating explaining what it's doing. I think the comments are mostly correct.

    This may be of benefit

    // promiseMethods. These are the methods you get when you ask for a promise.
    // A promise is a "read-only" version
    // fullMethods = "then done fail resolve resolveWith reject rejectWith isResolve    isRejected promise cancel".split(" ")
    // As you can see it removes resolve/reject so you can't actaully trigger a
    // anything on the deferred object, only process callbacks when it "finishes".
    promiseMethods = "then done fail isResolved isRejected promise".split(" "),
    
    // Create a simple deferred (one callbacks list)
    /* Class: _Deferred.
     *  methods: done, resolve, resolveWith, isResolved
     *  internal method: cancel
     *
     *  Basically allows you to attach callbacks with the done method.
     *  Then resolve the deferred action whenever you want with an argument.
     *  All the callbacks added with done will be called with the resolved argument
     *  Any callbacks attached after resolvement will fire immediatly.
     *
     *  resolveWith allows you to set the this scope in the callbacks fired.
     *
     *  isResolved just checks whether it's resolved yet.
     *
     *  cancel blocks resolve/resolveWith from firing. the methods added throug
     *  done will never be called
     */
    _Deferred: function () {
        var // callbacks list
        callbacks = [],
            // stored [ context , args ]
            // stores the context & args that .resolve was called with
            fired,
            // to avoid firing when already doing so
            firing,
            // flag to know if the deferred has been cancelled
            // in Deferred cancel gets called after the first resolve call
            cancelled,
            // the deferred itself
            deferred = {
    
                // done( f1, f2, ...)
                done: function () {
                    if (!cancelled) {
                        var args = arguments,
                            i, length,
                            // elem in callback list
                            elem,
                            // type of elem in callback list
                            type,
                            // cached context & args for when done is called
                            // after resolve has been
                            _fired;
                        // If resolve has been called already
                        if (fired) {
                            // mark it locally
                            _fired = fired;
                            // set fired to 0. This is neccesary to handle
                            // how done deals with arrays recursively
                            // only the original .done call handles fired
                            // any that unwrap arrays and call recursively
                            // dont handle the fired.
                            fired = 0;
                        }
                        // for each function append it to the callback list
                        for (i = 0, length = args.length; i < length; i++) {
                            elem = args[i];
                            type = jQuery.type(elem);
                            // if argument is an array then call done recursively
                            // effectively unwraps the array
                            if (type === "array") {
                                // def.done([f1, f2, f3]) goes to
                                // def.done(f1, f2, f3) through the apply
                                deferred.done.apply(deferred, elem);
                            } else if (type === "function") {
                                // if its a function add it to the callbacks
                                callbacks.push(elem);
                            }
                        }
                        // if it's already been resolved then call resolveWith using
                        // the cahced context and arguments to call the callbacks
                        // immediatly
                        if (_fired) {
                            deferred.resolveWith(_fired[0], _fired[1]);
                        }
                    }
                    return this;
                },
    
                // resolve with given context and args
                resolveWith: function (context, args) {
                                    // if its been cancelled then we can't resolve
                                    // if it has fired then we can't fire again
                                    // if it's currently firing then we can't fire. This check is
                    // there because of the try finally block. It ensures we
                    // cant call resolve between the try & finally in the catch phase.
                    if (!cancelled && !fired && !firing) {
                        firing = 1;
                        // try block because your calling external callbacks
                        // made by the user which are not bugfree.
                                            // the finally block will always run no matter how bad
                                            // the internal code is.
                        try {
                            while (callbacks[0]) {
                                callbacks.shift().apply(context, args);
                            }
                                            // cache the content and arguments taht have been called
                                            // and set firing to false.
                        } finally {
                            fired = [context, args];
                            firing = 0;
                        }
                    }
                    return this;
                },
    
                // resolve with this as context and given arguments
                // just maps to resolveWith, this sets the this scope as normal
                // maps to this.promise which is the read only version of Deferred.
                resolve: function () {
                    deferred.resolveWith(jQuery.isFunction(this.promise) ? this.promise() : 
    this, arguments);
                    return this;
                },
    
                // Has this deferred been resolved?
                // checks whether it's firing or if it has fired.
                isResolved: function () {
                    return !!(firing || fired);
                },
    
                // Cancels the action. To be used internally
                cancel: function () {
                    cancelled = 1;
                    callbacks = [];
                    return this;
                }
            };
    
        return deferred;
    },
    /* Class: Deferred.
     *  methods: then, done, fail, resolve, reject, resolveWith, rejectWith, isResolved, 
    isRejected, promise
     *
     *  then is a shortcut for both assigning done & fail in one function.
     *
     *  This one has two underlying lists with different semantic meanings. You
     *  can bind to both the done callbacks and the fail callbacks then either
     *  resolve or reject your Deferred object.
     *
     *  You can check whether it has been resolved or rejected. useful to see
     *  Afterwards which one has happened.
     *
     *  Call .promise to return a new object which doesn't have the resolve/reject
     *  methods on it. This means you can only bind to it and not resolve/reject it.
     *  This is effectively read-only.
     *
     */
    // Full fledged deferred (two callbacks list)
    Deferred: function (func) {
            // the main deferred which deals with the success callbacks
        var deferred = jQuery._Deferred(),
                    // the failure deferred which deals with the rejected callbacks
            failDeferred = jQuery._Deferred(),
                    // the read only promise is cached.
            promise;
        // Add errorDeferred methods, then and promise
        jQuery.extend(deferred, {
                    // def.then([f1, f2, ...], [g1, g2, ...] is a short hand for
                    // def.done([f1, f2, ...])
            // def.fail([g1, g2, ...])
            then: function (doneCallbacks, failCallbacks) {
                            // fail exists here because this code will only run after
                            // deferred has been extended.
                deferred.done(doneCallbacks).fail(failCallbacks);
                return this;
            },
                    // map def.fail to the second underlying deferred callback list
                    // map all the other methods for rejection/failure to the underlying
                    // failDeffered object so that Deferred has two callback lists stored
                    // internally.
            fail: failDeferred.done,
            rejectWith: failDeferred.resolveWith,
            reject: failDeferred.resolve,
            isRejected: failDeferred.isResolved,
            // Get a promise for this deferred
            // If obj is provided, the promise aspect is added to the object
                    // no clue what to do with "i"
            promise: function (obj, i /* internal */ ) {
                            // if no argument is passed then just extend promise
                if (obj == null) {
                                    // if cached return the cache.
                    if (promise) {
                        return promise;
                    }
                                    // set promise & arg to be {}
                    promise = obj = {};
                }
                            // for each promiseMethods in the read only promise list
                i = promiseMethods.length;
                while (i--) {
                                    // set the deferred method on the object
                    obj[promiseMethods[i]] = deferred[promiseMethods[i]];
                }
                            // returns the "read-only" deferred without
                            // resolve, resolveWith, reject & rejectWith.
                            // So you cant "resolve" it but only add "done" functions
                return obj;
            }
        });
        // Make sure only one callback list will be used
            // if either resolve or reject is called cancel both.
            // this means that the one that has been called cant be called again
            // and the other one will never be called. So only the done or the fail
            // methods will ever be called
        deferred.then(failDeferred.cancel, deferred.cancel);
            // Don't mess with cancel!
        // Unexpose cancel
        delete deferred.cancel;
        // Call given func if any
            // function argument to be called. This was passed in. Allows you to
            // handle the deferred object after creating a new one, both as this scope
            // and as a new argument.
        if (func) {
            func.call(deferred, deferred);
        }
        return deferred;
    },
    
    /* Method: when
     * Arguments: none OR 1 of type(any & !deferred) OR n of type(deferred).
     *
     * If no arguments are passed then it gets resolved immediatly. A good way to
     * call multiple callback functions? Don't really know a good use of $.when()
     *
     * If one argument is passed and its not a deferred object then it resolves
     * immediatly and passes that argument to all the done callbacks attached.
     *
     * if n arguments are passed of type deferred object then the the done callbacks
     * will only fire if all of them succeed. If a single one fails then the
     * fail callbacks fire.
     *
     * Returns a promise read-only deferred object
     */
    // Deferred helper
    when: function (object) {
        var args = arguments,
            length = args.length,
                    // If you pass in a deferred object then set deferred to be the promise
            // if you pass in anything else then set deferred to be a new deferred
            deferred = length <= 1 && object && jQuery.isFunction(object.promise) ?
                    object :
                            jQuery.Deferred(),
            // cache the promise
            promise = deferred.promise(),
                    // store an array
            resolveArray;
    
            // if multiple objects are passed in
        if (length > 1) {
                    // create an arrey to store of values.
            resolveArray = new Array(length);
                    // for each object that we wait on
            jQuery.each(args, function (index, element) {
                            // when that object resolves then
                jQuery.when(element).then(function (value) {
                                    // store value in the array or store an array of values in it
                    resolveArray[index] = arguments.length > 1 ? slice.call(arguments, 0) : 
    value;
                                    // if length === 1 then we finished calling them all
                    if (!--length) {
                                            // resolve the deferred object with the read only promise
                                            // as context and the resolved values array as the argument
                        deferred.resolveWith(promise, resolveArray);
                    }
                            // if any fail then we reject or deferred
                }, deferred.reject);
            });
            // if deferred was newly created but there was only one argument then
        // resolve it immediatly with the argument.
        } else if (deferred !== object) {
            deferred.resolve(object);
        }
            // return the read-only deferred.
        return promise;
    },
    

    Correct me if i'm wrong, but it recently clicked for me that it's essentially an Asynchronous Task Runner. The promise is a result contract, ensuring you receive ...something, but without guarantee of when you'll get it.

    链接地址: http://www.djcxy.com/p/1622.html

    上一篇: 如何同步AJAX调用可能导致内存泄漏?

    下一篇: 什么是延迟对象?