讀jQuery之二十(Deferred對象)


Deferred對象是由 jQuery.Deferred 構造的,jQuery.Deferred 被實現為簡單工廠模式

它用來解決JS中的異步編程,它遵循 Common Promise/A 規范。實現此規范的還有 when.js 和 dojo

 

$.Deferred作為新特性首次出現在版本1.5中,這個版本利用Deferred又完全重寫了Ajax模塊。

$.Deferred在jQuery代碼自身四處被使用,分別是promise方法、DOM readyAjax模塊、動畫模塊。

這里以版本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的實現

  1. 創建三個$.Callbacks對象,分別表示成功,失敗,處理中三種狀態
  2. 創建了一個promise對象,具有state、always、then、primise方法
  3. 通過擴展primise對象生成最終的Deferred對象,返回該對象

 

$.when的實現

  1. 接受若干個對象,參數僅一個且非Deferred對象將立即執行回調函數
  2. Deferred對象和非Deferred對象混雜時,對於非Deferred對象remaining減1
  3. Deferred對象總數 = 內部構建的Deferred對象 + 所傳參數中包含的Deferred對象
  4. 所傳參數中所有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://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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM