Skip to main content
 首页 » 编程设计

jQuery源码分析之Deferred对象和Promise对象

2022年07月16日148mq0036
jQuery.extend({ 
        Deferred: function(func) { 
            // 数据集中管理 
            var tuples = [ 
                ['resolve', 'done', jQuery.Callbacks('once memory'), 'resolved'], 
                ['reject', 'fail', jQuery.Callbacks('once memory'), 'rejected'], 
                ['notify', 'progress', jQuery.Callbacks('memory')] 
            ], 
                state = 'pending', 
                promise = { 
                    /** 
                     * 返回一个字符串,代表Deferred(延迟)对象的当前状态 
                     * 
                     * @returns {string} "pending"( Deferred对象是尚未完成状态) || "rejected"(Deferred对象是在被拒绝的状态) || "resolved"(Deferred对象是在解决状态) 
                     */ 
                    state: function() { 
                        return state; 
                    }, 
                    /** 
                     * 当Deferred(延迟)对象解决或拒绝时,调用添加处理程序 
                     * 即如果调用后不管成功还是失败,都会执行该回调 
                     */ 
                    always: function() { 
                        deferred.done(arguments).fail(arguments); 
                        return this; 
                    }, 
                    /** 
                     *  
                     * then方法会返回一个新的Deferred对象 
                     * 如果then方法的参数是deferred对象, 
                     * 上一链的旧deferred会调用[ done | fail | progress ]方法注册回调,该回调内容是:执行when方法对应的参数回调(fnDone, fnFail, fnProgress)。 
                     * 1)如果参数回调执行后返回的结果是一个deferred对象,我们就给该deferred对象相应的回调列表添加回调,该回调是触发when方法返回的新deferred对象的成功,失败,处理中(done,fail,progress)的回调列表中的所有回调。 
                     * 当我们再给then方法进行链式地添加回调操作(done,fail,progress,always,then)时,就是给新deferred对象注册回调到相应的回调列表。 
                     * 如果我们then参数fnDoneDefer, fnFailDefer, fnProgressDefer得到了解决,就会执行后面链式添加回调操作中的参数函数。 
                     *  
                     * 2)如果参数回调执行后返回的结果不是deferred对象,就立刻触发新deferred对象相应回调列表的所有回调。 
                     * 当我们再给then方法进行链式地添加回调操作(done,fail,progress,always,then)时,就会立刻触发我们添加的相应的回调。 
                     *  
                     * 可以多个then连续使用,此功能相当于顺序调用异步回调。 
                     * 
                     * @example 
                     * $.ajax({ 
                           url: 't2.html', 
                           dataType: 'html', 
                           data: { 
                              d: 4 
                           } 
                        }).then(function(){ 
                            console.log('success'); 
                        },function(){ 
                            console.log('failed'); 
                        }).then(function(){ 
                            console.log('second'); 
                            return $.ajax({ 
                                url: 'jquery-1.9.1.js', 
                                dataType: 'script' 
                            }); 
                        }, function(){ 
                            console.log('second f'); 
                            return $.ajax({ 
                                url: 'jquery-1.9.1.js', 
                                dataType: 'script' 
                            }); 
                        }).then(function(){ 
                            console.log('success2'); 
                        },function(){ 
                            console.log('failed2'); 
                        }); 
                        @notification 当前面的deferred没有被解决即失败,后面的就会执行失败回调,如果then方法中传了两个可返回deferred对象的回调,上一个deferred失败就会触发第二个返回的deferred,如果只传了一个返回deferred的回调,则后面都会报错。后面的失败却不会影响前面的。 
                     * @returns {*} 
                     */ 
                    then: function( /* fnDone, fnFail, fnProgress */ ) { 
                        var fns = arguments; 
                        // 返回一个新的Deferred对象的promise对象 
                        return jQuery.Deferred(function(newDefer) { 
                            // newDefer其实就是一个新的deferred对象 
                            jQuery.each(tuples, function(i, tuple) { 
                                var 
                                // "resolve" | "reject" | "notify" 
                                action = tuple[0], 
                                    fn = jQuery.isFunction(fns[i]) && fns[i]; 
 
                                // 运行deferred[ done | fail | progress ]方法, 
                                // 将回调函数添加到相应回调列表 
                                deferred[tuple[1]](function() { 
                                    // 执行当前参数回调并获取其返回值 
                                    var returned = fn && fn.apply(this, arguments); 
                                    // 如果returned有返回值且有promise方法, 
                                    // 说明是一个deferred对象, 
                                    // 则将newDefer对象的三个回调列表的触发器添加到returned对象的相应列表中 
                                    if (returned && jQuery.isFunction(returned.promise)) { 
                                        returned.promise(). 
                                        done(newDefer.resolve). 
                                        fail(newDefer.reject). 
                                        progress(newDefer.notify); 
                                    } else { 
                                        // 否则就触发newDefer对象的相应回调列表触发器 
                                        // 同时确保this指向newDefer的promise对象 
                                        // 要是这个newDefer有内容触发需要再在后面将回调添加到响应列表中, 
                                        // 所以这个是执行下一个deferred的操作 
                                        newDefer[action + 'With'](this === promise ? newDefer.promise() : this, fn ? [returned] : arguments); 
                                    } 
                                }); 
                            }); 
                            // 销毁对象 
                            fns = null; 
                        }).promise(); 
                    }, 
                    /* 
                     如果有参数返回参数对象继承了promise对象属性的对象, 
                     否则返回该Deferred对象中的promise对象 
                     */ 
                    promise: function(obj) { 
                        return obj != null ? jQuery.extend(obj, promise) : promise; 
                    } 
                }, 
                deferred = {}; 
 
            // 备份原始对象 
            promise.pipe = promise.then; 
 
            // 给deferred对象添加方法 
            jQuery.each(tuples, function(i, tuple) { 
                var 
                // jQuery.Callbacks() 
                list = tuple[2], 
                    // "resolved" or "rejected" 
                    stateString = tuple[3]; 
 
                // 给promise对象添加"done", "fail", "progress"方法 
                // 当使用这些方法实际上就是给所在回调列表添加回调 
                // 注意:list.add方法里面的this已经指向了promise 
                // 因此可以deferred.done(arguments).fail(arguments)的链式操作 
                promise[tuple[1]] = list.add; 
 
                // 如果是"resolved"或者"rejected" 
                if (stateString) { 
                    // 给相应的回调列表添加以下三个回调函数,回调列表状态机 
                    // 第一个是将异步队列状态传给state变量 
                    // 第二个方法是将其他状态的列表禁用 
                    // 第三个是锁定“progress”的回调列表 
                    // 例如是“resolved”则禁用“rejected”的回调列表, 
                    // 锁定“progress”的回调列表 
                    list.add(function() { 
                        state = stateString; 
                    }, tuples[i ^ 1][2].disable, tuples[2][2].lock); 
                } 
 
                // 添加deferred[ resolve | reject | notify ]方法 
                deferred[tuple[0]] = function() { 
                    // 实际上是运行deferred[ resolveWith | rejectWith | notifyWith ]方法 
                    // 同时确保上下文是deferred对象 
                    deferred[tuple[0] + 'With'](this === deferred ? promise : this, arguments); 
                    // 链式操作 
                    return this; 
                }; 
                // 添加deferred[ resolveWith | rejectWith | notifyWith ]方法 
                // 这些方法就是所在回调列表的fireWith方法 
                // 通过给定上下文触发列表所有回调函数 
                deferred[tuple[0] + 'With'] = list.fireWith; 
            }); 
 
            // 给deferred对象添加promise对象的所有属性 
            promise.promise(deferred); 
 
            if (func) { 
                // 运行该函数,this和arguments都是deferred对象 
                func.call(deferred, deferred); 
            } 
 
            // 返回deferred对象 
            // 该对象现有 [ resolve | reject | notify | resolveWith | rejectWith | notifyWith ] 
            // 以及从promise继承的 [ done | fail | then | promise | pipe | always | progress | state ] 
            // 这些方法 
            return deferred; 
        }, 
        /** 
         * 对多个deferred对象进行并行化操作,当所有deferred对象都得到解决就执行后面添加的相应回调。 
         */ 
        when: function(subordinate /* , ..., subordinateN */ ) { 
            var i = 0, 
                // 把arguments转换成数组 
                resolveValues = core_slice.call(arguments), 
                // 参数的长度 
                length = resolveValues.length, 
                // 如果长度不等于1或者第一个参数是deferred对象, 
                // 返回true,最后返回正常长度 
                // 否则返回0 
                // 说明参数必须是deferred对象,而remaining是记录执行剩余的长度 
                remaining = length !== 1 || (subordinate && jQuery.isFunction(subordinate.promise)) ? length : 0, 
                // 主要的Deferred对象。如果resolveValues只有一个Deferred对象 
                // 使用该对象,否则新建一个Deferred对象 
                deferred = remaining === 1 ? subordinate : jQuery.Deferred(), 
                // 当为resolve或者progress的情况时的处理函数 
                updateFunc = function(i, contexts, values) { 
                    return function(value) { 
                        contexts[i] = this; 
                        // values是when参数数组或者progressValues 
                        // 如果参数大于一,values[i]为参数数组 
                        // 以$.ajax为例,$.ajax的解决回调的参数values[i] = [response, 'success', jqXHR] 
                        // 所以在下次我们给列表添加回调时,回调的参数类似于这样[firsetAjaxCallbackArgs, secondAjaxCallbackArgs] 
                        // firsetAjaxCallbackArgs为第一个ajax的[resposne, 'success', jqXHR] 
                        // 以此类推 
                        /** 
                         * $.when( 
                               $.ajax({ 
                                url: 't2.html' 
                            }), 
                             $.ajax({ 
                                url: 'jquery-1.9.1-study.js' 
                            }) 
                            ).then(function(                    FirstAjaxSuccessCallbackArgs, SecondAjaxSuccessCallbackArgs){ 
                                 console.log('success'); 
                            }, function(){ 
                                console.log('failed'); 
                            }); 
                         */ 
                        values[i] = arguments.length > 1 ? core_slice.call(arguments) : value; 
                        // progress 
                        if (values === progressValues) { 
                            deferred.notifyWith(contexts, values); 
 
                            // 将remaining减一,说明该when参数的deferred已经回调注册完毕 
                            // 如果这时remaining小于等于0, 
                            // 说明我们正在解决最后一个参数deferred, 
                            // 就立刻触发deferred的回调,注意这里的deferred不是参数的deferred 
                            // 这里就达到了并行的效果了 
                        } else if (!(--remaining)) { 
                            // deferred需要在外部给回调列表注册回调 
                            // resolve 
                            deferred.resolveWith(contexts, values); 
                        } 
                    }; 
                }, 
                progressValues, progressContexts, resolveContexts; 
 
            // 当至少有两个Deferred对象时 
            if (length > 1) { 
                progressValues = new Array(length); 
                progressContexts = new Array(length); 
                resolveContexts = new Array(length); 
                for (; i < length; i++) { 
                    // 遍历,如果是deferred对象,给when参数中当前的deferred的回调列表添加回调 
                    if (resolveValues[i] && jQuery.isFunction(resolveValues[i].promise)) { 
                        resolveValues[i].promise() 
                            .done(updateFunc(i, resolveContexts, resolveValues)) 
                            .fail(deferred.reject) 
                            .progress(updateFunc(i, progressContexts, progressValues)); 
                    } else { 
                        // 否则不是deferred对象直接将remaining的长度-1 
                        --remaining; 
                    } 
                } 
            } 
 
            // 当remaining为0的时候,也就是length===1或者when参数没有deferred对象时,立刻触发 
            if (!remaining) { 
                deferred.resolveWith(resolveContexts, resolveValues); 
            } 
 
            // 返回promise对象 
            return deferred.promise(); 
        } 
    }); 

  

 这有一篇有关Deferred的讲得很好的文章,阮老师的:http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html

可以帮助更好了解Deferred对象的作用。

USAGE:

 1 下面是部分Deferred对象的API使用方法: 
 2 1.deferred.done(doneCallbacks[,doneCallbacks])与deferred.fail(failCallbacks[,failCallbacks]) 
 3  
 4 一旦jQuery.get方法返回一个jqXHR对象,这是从一个递延所得,可以附加的成功和失败回调使用deferrred.done()和deferred.fail()方法。 
 5  
 6 $.get("test.php") 
 7   .done(function(){ alert("$.get succeeded"); })//延迟成功 
 8   .fail(function(){ alert("$.get failed!"); });//延迟失败 
 9  
10  
11 2.deferred.then(doneCallbacks,failCallbacks[, progressCallbacks]) 
12  
13 一旦jQuery.get方法返回一个来自延迟的对象的jqXHR对象,我们可以附加一个成功回调使用.then方法。 
14  
15 $.get("test.php").then( 
16     function(){ alert("$.get succeeded"); }, 
17     function(){ alert("$.get failed!"); } 
18 ); 
19  
20  
21 3.deferred.always(alwaysCallbacks,[alwaysCallbacks]) 
22  
23 jQuery.get()方法返回一个来自一个延迟的对象的jqXHR对象,我们可以附加一个成功和错误使用deferred.always()方法的回调。 
24  
25 $.get("test.php").always( function() {  
26   alert("$.get completed with success or error callback arguments");  
27 } ); 
28  
29 1.jQuery.when(deferreds) 
30  
31 $.when( $.ajax("test.aspx") ).then(function(ajaxArgs){  
32      alert(ajaxArgs[1]); /* ajaxArgs is [ "success", statusText, jqXHR ] */ 
33 }); 
34  
35 $.when( { testing: 123 } ).done( 
36    function(x){ alert(x.testing); } /* alerts "123" */ 
37 ); 
38  
39 $.when($.ajax("/page1.php"), $.ajax("/page2.php")).done(function(a1,  a2){ 
40     /* a1 and a2 are arguments resolved for the  
41         page1 and page2 ajax requests, respectively */ 
42    var jqXHR = a1[2]; /* arguments are [ "success", statusText, jqXHR ] */ 
43    if ( /Whip It/.test(jqXHR.responseText) ) { 
44       alert("First page has 'Whip It' somewhere."); 
45    } 
46 }); 
47  
48 $.when($.ajax("/page1.php"), $.ajax("/page2.php")) 
49   .then(myFunc, myFailure);

本文参考链接:https://www.cnblogs.com/webFrontDev/archive/2013/05/14/3078703.html