Deferred對象是由 jQuery.Deferred 構造的,jQuery.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行左右。
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) .progress(handling) .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://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