读jQuery之二十（Deferred对象）
李小翔
2015-10-27 09:54

内容列表
$.Deferred的实现 	 # 
$.when的实现 	 # 

原文，另推荐阅读《使用jQuery Deferred 和 Promise 创建响应式应用程序》
Deferred对象是由$.Deferred构造的，$.Deferred被实现为简单工厂模式。
它用来解决JS中的异步编程，它遵循 Common Promise/A 规范。实现此规范的还有 when.js 和 dojo。
 
$.Deferred作为新特性首次出现在版本1.5中，这个版本利用Deferred又完全重写了Ajax模块。
$.Deferred在jQuery代码自身四处被使用，分别是promise方法、DOM ready、Ajax模块、动画模块。
这里以版本1.8.3分析，由于1.7后$.Callbacks从Deferred中抽离出去了，目前版本的deferred.js代码不过150行，而真正$.Deferred的实现只有100行左右。
 
$.extend给标示符$上挂了两个方法，如下
jQuery.extend({ 
    Deferred: function( func ) { 
       var tuples = [
               // action, add listener, listener list, final state
                [ ”resolve”, ”done”, jQuery.Callbacks(”once memory”), ”resolved” ],
                [ ”reject”, ”fail”, jQuery.Callbacks(”once memory”), ”rejected” ],
                [ ”notify”, ”progress”, jQuery.Callbacks(”memory”) ]
            ],
        ...
       // All done!
        return deferred;
    } ,
   // Deferred helper
    when: function( subordinate /* , ..., subordinateN */ ) { 
       var i = 0,
            resolveValues = core_slice.call( arguments ),
            length = resolveValues.length,
            ....
       return deferred.promise();
    } 
} );

$.Deferred的实现
· 	创建三个$.Callbacks对象，分别表示成功，失败，处理中三种状态
· 	创建了一个promise对象，具有state、always、then、primise方法
· 	通过扩展primise对象生成最终的Deferred对象，返回该对象
· 	
$.when的实现
· 	接受若干个对象，参数仅一个且非Deferred对象将立即执行回调函数
· 	Deferred对象和非Deferred对象混杂时，对于非Deferred对象remaining减1
· 	Deferred对象总数 = 内部构建的Deferred对象 + 所传参数中包含的Deferred对象
· 	所传参数中所有Deferred对象每当resolve时remaining减1，直到为0时（所有都resolve）执行回调
· 	这就是.Deferred和.when的全部了，各个方法及使用稍后介绍。
代码阅读中会发现then和when方法的实现最难理解，看多次，后感回味无穷，非常巧妙。then内部会用到不同寻常的递归，when用到了计数，每次异步成功后减一，直到为0后表示全部异步操作成功，这时才可执行回调。
 上面提到Deferred里有3个$.Callbacks的实例，Deferred自身则围绕这三个对象进行更高层次的抽象。以下是Deferred对象的核心方法
done/fail/progress 是 callbacks.add，将回调函数存入
resolve/reject/notify 是 callbacks.fire，执行回调函数(或队列)
 
下面举一些示例看看如何使用Deferred对象。
 
一、done/resolve
function cb() { 
    alert(’success’)
} 
var deferred = $.Deferred()
deferred.done(cb)
setTimeout(function() { 
    deferred.resolve()
} , 3000)
在HTTP中表示后台返回成功状态（如200）时使用，即请求成功后可执行成功回调函数。
 
二、fail/reject
function cb() { 
    alert(’fail’)
} 
var deferred = $.Deferred()
deferred.fail(cb)
setTimeout(function() { 
    deferred.reject()
} , 3000)
在HTTP中表示后台返回非成功状态时使用，即请求失败后可执行失败回调函数。
 
三、progress/notify
function cb() { 
    alert(’progress’)
} 
var deferred = $.Deferred()
deferred.progress(cb)
setInterval(function() { 
    deferred.notify()
} , 2000)
在HTTP中表示请求过程中使用，即请求过程中不断执行回调函数。这可用在文件上传时的loading百分比或进度条。
 
四、链式操作
function fn1() { 
    alert(’success’)
} 
function fn2() { 
    alert(’fail’)
} 
function fn3() { 
    alert(’progress’)
} 
var deferred = $.Deferred()
deferred.done(fn1).fail(fn2).progress(fn3) // 链式操作
setTimeout(function() { 
    deferred.resolve()
   //deferred.reject()
    //deferred.notify()
} , 3000)
这样可以很方便了添加成功，失败，进度回调函数。
 
五，便利函数then，一次添加成功，失败，进度回调函数
function fn1() { 
    alert(’success’)
} 
function fn2() { 
    alert(’fail’)
} 
function fn3() { 
    alert(’progress’)
} 
var deferred = $.Deferred()
deferred.then(fn1, fn2, fn3)
调用then后还可以继续链式调用then添加多个不同回调函数，这个then也正是jQuery对 Common Promise/A 的实现。
 
六、使用always方法为成功，失败状态添加同一个回调函数
var deferred = $.Deferred()
deferred.always(function() { 
   var state = deferred.state()
   if ( state === ’resolved’) { 
        alert(’success’)
    }  else if (state === ’rejected’) { 
        alert(’fail’)
    } 
} )
setTimeout(function() { 
    deferred.resolve()
   //deferred.reject()
} , 3000)
回调函数中可以使用deferred.state方法获取异步过程中的最终状态，这里我调用的是deferred.resolve，因此最后的状态是resolved，表示成功。
 
七、when方法保证多个异步操作全部成功后才回调
function fn1() { 
    alert(’done1’)
} 
function fn2() { 
    alert(’done2’)
} 
function fn3() { 
    alert(’all done’)
} 
var deferred1 = $.Deferred()
var deferred2 = $.Deferred()
deferred1.done(fn1)
deferred2.done(fn2)
$.when(deferred1, deferred2).done(fn3)
setTimeout(function() { 
    deferred1.resolve()
    deferred2.resolve()
} , 3000)
先后弹出了done1、done2、all done。 如果setTimeout中有一个reject了，fn3将不会被执行。
八、deferred.promise()方法返回只能添加回调的对象，这个对象与$.Deferred()返回的对象不同，只能 done/fail/progress，不能resolve/reject/notify。即只能调用callbacks.add，没有 callbacks.fire。它是正统Deferred对象的阉割版。
有了Deferred，我们使用jQuery书写ajax的风格可以这样了
$.ajax(url)
 .done(success)
 .fail(fail)
看似和以前比较也没什么优点，但它还可以添加多个回调
$.ajax(url)
 .done(success1)
 .done(success2)
 .fail(fail2)
 .fail(fail2)
1.5之前的则不行
如果多个请求完成后才算成功，1.5之前的是无法解决的，现在则可以用$.when搞定
var ajax1 = $.ajax(url1)
var ajax2 = $.ajax(url2)
$.when(ajax1, ajax2).done(success)
如果项目中有一些异步问题不妨用用Derferred。
相关：
http://jimliu.net/?p=64
http://www.infoq.com/cn/news/2011/09/js-promise
http://www.erichynds.com/jquery/using-deferreds-in-jquery/
http://sitr.us/2012/07/31/promise-pipelines-in-javascript.html
http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html