Deferred
模塊也不是必備的模塊,但是 ajax
模塊中,要用到 promise
風格,必需引入 Deferred
模塊。Deferred
也用到了上一篇文章《讀Zepto源碼之Callbacks模塊》介紹的 Callbacks
模塊。
讀 Zepto 源碼系列文章已經放到了github上,歡迎star: reading-zepto
源碼版本
本文閱讀的源碼爲 zepto1.2.0
Promise/A+ 規範
規範的具體內容可以參考《Promises/A+》 和對應的中文翻譯 《Promise/A+規範》,這裏只簡單總結一下。
promise
是一個包含兼容 promise
規範的函數或對象,promise
包含三種狀態 pending
進行中、fulfilled
已完成, rejected
被拒絕,並且必須處於其中一種狀態。
pending
狀態可以轉換成 fulfilled
狀態或者 rejected
狀態,但是 fulfilled
狀態和 rejected
狀態不能再轉換成其他狀態。
promise
必須包含 then
方法,then
方法可以接收兩個參數,參數類型都爲函數,分別爲狀態變爲 fulfilled
後調用的 onFulfilled
函數和 rejected
後調用的 onRejected
函數。
大致瞭解 Promise/A+
規範後,對後面源碼的閱讀會有幫助。
Deferred 模塊的整體結構
;(function($){
function Deferred(func) {
deferred = {}
if (func) func.call(deferred, deferred)
return deferred
}
return $.Deferred = Deferred
})(Zepto)
從上面的精簡的結構可以看出,Deferred
是一個函數,函數的返回值是一個符合 Promise/A+
規範的對象,如果 Deferred
有傳遞函數作爲參數,則以 deferred
作爲上下文,以 deferred
作爲參數執行該函數。
done、fail、progress、resolve/resolveWith、reject/rejectWith、notify/notifyWith 方法的生成
var tuples = [
// action, add listener, listener list, final state
[ "resolve", "done", $.Callbacks({once:1, memory:1}), "resolved" ],
[ "reject", "fail", $.Callbacks({once:1, memory:1}), "rejected" ],
[ "notify", "progress", $.Callbacks({memory:1}) ]
],
state = "pending",
promise = {
...
}
deferred = {}
$.each(tuples, function(i, tuple){
var list = tuple[2],
stateString = tuple[3]
promise[tuple[1]] = list.add
if (stateString) {
list.add(function(){
state = stateString
}, tuples[i^1][2].disable, tuples[2][2].lock)
}
deferred[tuple[0]] = function(){
deferred[tuple[0] + "With"](this === deferred ? promise : this, arguments)
return this
}
deferred[tuple[0] + "With"] = list.fireWith
})
變量解釋
- tuples: 用來儲存狀態切換的方法名,對應狀態的執行方法,回調關係列表和最終的狀態描述。
- state: 狀態描述
- promise:
promise
包含執行方法always
、then
、done
、fail
、progress
和輔助方法state
、promise
等 - deferred:
deferred
除了繼承promise
的方法外,還增加了切換方法,resolve
、resoveWith
、reject
、rejectWith
、notify
、notifyWith
。
done、fail和progress的生成
$.each(tuples, function(i, tuple){
...
})
方法的生成,通過遍歷 tuples
實現
var list = tuple[2],
stateString = tuple[3]
promise[tuple[1]] = list.add
list
是工廠方法 $.Callbacks
生成的管理回調函數的一系列方法。具體參見上一篇文章《讀Zepto源碼之Callbacks模塊》。注意,tuples
的所有項中的 $Callbacks
都配置了 memory:1
,即開啓記憶模式,增加的方法都會立即觸發。包含 resove
和 reject
的項都傳遞了 once:1
,即回調列表只能觸發一次。
stateString
是狀態描述,只有包含了 resolve
和 reject
的數組項才具有。
index
爲 1
的項,取出來的分別爲 done
、 fail
和 progress
,所以 promise
上的 done
、 fail
和 progress
方法,調用的是 Callbacks
中的 add
方法,實質是往各自的回調列表中添加回調函數。
狀態切換
if (stateString) {
list.add(function(){
state = stateString
}, tuples[i^1][2].disable, tuples[2][2].lock)
}
如果 stateString
存在,即包含 resolve
和 reject
的數組項,則往對應的回調列表中添加切換 state
狀態的方法,將 state
更改爲對應方法觸發後的狀態。
同時,將狀態鎖定,即狀態變爲 resolved
或 rejected
狀態後,不能再更改爲其他狀態。這裏用來按位異或運算符 ^
來實現。當 i
爲 0
,即狀態變爲 resolved
時, i^1
爲 1
。tuples[i^1][2].disable
將 rejected
的回調列表禁用,當 i
爲 1
時, i^1
爲 0
,將 resolved
的回調列表禁用。即實現了成功和失敗的狀態互斥,做得狀態鎖定,不能再更改。
在狀態變更後,同時將 tuples[2]
的回調列表鎖定,要注意 disable
和 lock
的區別,具體見《讀Zepto源碼之Callbacks模塊》,由於這裏用了記憶模式,所以還可以往回調列表中添加回調方法,並且回調方法會立即觸發。
resolve/resolveWith、reject/rejectWith、notify/notifyWith 方法的生成
deferred[tuple[0] + "With"] = list.fireWith
deferred[tuple[0]] = function(){
deferred[tuple[0] + "With"](this === deferred ? promise : this, arguments)
return this
}
這幾個方法,存入在 deferred
對象中,並沒有存入 promise
對象。
resolveWith
、 rejectWith
和 notifyWith
方法,其實等價於 Callback
的 fireWith
方法,fireWith
方法的第一個參數爲上下文對象。
從源碼中可以看到 resolve
、reject
和 notify
方法,調用的是對應的 With
後綴方法,如果當前上下文爲 deferred
對象,則傳入 promise
對象作爲上下文。
promise 對象
.state()
state: function() {
return state
},
state
方法的作用是返回當前的狀態。
.always()
always: function() {
deferred.done(arguments).fail(arguments)
return this
},
always
是一種省事的寫法,即無論成功還是失敗,都會執行回調。調用的是 deferred
上的 done
和 fail
方法。或許你會有疑惑,done
和 fail
方法,上面的分析中,明明是 promise
的方法,爲什麼 deferred
對象上也有這兩個方法呢,這個下面會講到。
.promise()
promise: function(obj) {
return obj != null ? $.extend( obj, promise ) : promise
}
返回 promise
對象,如果 obj
有傳遞,則將 promise
上的方法擴展到 obj
上。
.then()
then: function(/* fnDone [, fnFailed [, fnProgress]] */) {
var fns = arguments
return Deferred(function(defer){
$.each(tuples, function(i, tuple){
var fn = $.isFunction(fns[i]) && fns[i]
deferred[tuple[1]](function(){
var returned = fn && fn.apply(this, arguments)
if (returned && $.isFunction(returned.promise)) {
returned.promise()
.done(defer.resolve)
.fail(defer.reject)
.progress(defer.notify)
} else {
var context = this === promise ? defer.promise() : this,
values = fn ? [returned] : arguments
defer[tuple[0] + "With"](context, values)
}
})
})
fns = null
}).promise()
}
promise
的 then
方法接收三個參數,分別爲成功的回調、失敗的回調和進度的回調。
then整體結構
將 then
簡化後,可以看到以下的結構:
return Deferred(function(defer){}).promise()
返回的是 deferred
對象,deferred
對象上的 promise
方法,其實就是 promise
對象上的 promise
方法,所以 then
方法,最終返回的還是 promise
對象。所以 promise
可以這樣一直調用下去 promise().then().then()....
。
Deferred 調用
var fns = arguments
return Deferred(function(defer) {
...
})
fns = null
這裏的變量 fns
是 then
所傳入的參數,即上文提到的三個回調。
最後的 fns = null
,是釋放引用,讓 JS
引擎可以進行垃圾回收。
Deferred
的參數是一個函數,上文在分析總體結構的時候,有一句關鍵的代碼 if (func) func.call(deferred, deferred)
。所以這裏的函數的參數 defer
即爲 deferred
對象。
執行回調
$.each(tuples, function(i, tuple){
var fn = $.isFunction(fns[i]) && fns[i]
deferred[tuple[1]](function(){
var returned = fn && fn.apply(this, arguments)
if (returned && $.isFunction(returned.promise)) {
returned.promise()
.done(defer.resolve)
.fail(defer.reject)
.progress(defer.notify)
} else {
var context = this === promise ? defer.promise() : this,
values = fn ? [returned] : arguments
defer[tuple[0] + "With"](context, values)
}
})
})
遍歷 tuples
, tuples
中的順序,跟 then
中規定 done
、fail
和 progress
的回調順序一致。
所以用 var fn = $.isFunction(fns[i]) && fns[i]
來判斷對應位置的參數是否爲 function
類型,如果是,則賦值給 fn
。
deferred[tuple[1]]
是對應的是 done
、fail
和 progress
。所以在 then
裏,會依次執行這三個方法。
var returned = fn && fn.apply(this, arguments)
returned
是 then
中三個回調方法執行後返回的結果。
if (returned && $.isFunction(returned.promise)) {
returned.promise()
.done(defer.resolve)
.fail(defer.reject)
.progress(defer.notify)
}
如果回調返回的是 promise
對象,調用新 promise
對象中的 promise
方法,新 promise
對象切換狀態時, 並將當前 deferred
對象對應的狀態切換方法傳入,在新 promise
切換狀態時執行。這就實現了兩個 promise
對象的狀態交流。
var context = this === promise ? defer.promise() : this,
values = fn ? [returned] : arguments
defer[tuple[0] + "With"](context, values)
如果返回的不是 promise
對象,則判斷 this
是否爲 promise
,如果是,則返回 defer.promise()
,修正執行的上下文。
然後調用對應的狀態切換方法切換狀態。
promise 對象與 deferred 對象
promise.promise(deferred)
從上面的分析中,可以看到,deferred
對象上並沒有done
、 fail
和 progress
方法,這是從 promise
上擴展來的。
既然已經有了一個擁有 promise
對象的所有方法的 deferred
對象,爲什麼還要一個額外的 promise
對象呢?
promise
對象上沒有狀態切換方法,所以在 then
中,要綁定上下文的時候時候,綁定的都是 promise
對象,這是爲了避免在執行的過程中,將執行狀態改變。
$.when
$.when = function(sub) {
var resolveValues = slice.call(arguments),
len = resolveValues.length,
i = 0,
remain = len !== 1 || (sub && $.isFunction(sub.promise)) ? len : 0,
deferred = remain === 1 ? sub : Deferred(),
progressValues, progressContexts, resolveContexts,
updateFn = function(i, ctx, val){
return function(value){
ctx[i] = this
val[i] = arguments.length > 1 ? slice.call(arguments) : value
if (val === progressValues) {
deferred.notifyWith(ctx, val)
} else if (!(--remain)) {
deferred.resolveWith(ctx, val)
}
}
}
if (len > 1) {
progressValues = new Array(len)
progressContexts = new Array(len)
resolveContexts = new Array(len)
for ( ; i < len; ++i ) {
if (resolveValues[i] && $.isFunction(resolveValues[i].promise)) {
resolveValues[i].promise()
.done(updateFn(i, resolveContexts, resolveValues))
.fail(deferred.reject)
.progress(updateFn(i, progressContexts, progressValues))
} else {
--remain
}
}
}
if (!remain) deferred.resolveWith(resolveContexts, resolveValues)
return deferred.promise()
}
when
方法用來管理一系列的異步隊列,如果所有的異步隊列都執行成功,則執行成功方法,如果有一個異步執行失敗,則執行失敗方法。這個方法也可以傳入非異步方法。
一些變量
var resolveValues = slice.call(arguments),
len = resolveValues.length,
i = 0,
remain = len !== 1 || (sub && $.isFunction(sub.promise)) ? len : 0,
deferred = remain === 1 ? sub : Deferred(),
progressValues, progressContexts, resolveContexts,
- resolveValues:所有的異步對象,用
slice
轉換成數組形式。 - len: 異步對象的個數。
- remain: 剩餘個數。這裏還有個判斷,是爲了確定只有一個參數時,這個參數是不是異步對象,如果不是,則
remain
初始化爲0
。其他情況,初始化爲當前的個數。 - i: 當前異步對象執行的索引值。
- deferred:
deferred
對象,如果只有一個異步對象(只有一個參數,並且不爲異步對象時,remain
爲0
),則直接使用當前的deferred
對象,否則創建一個新的deferred
對象。 - progressValues: 進度回調函數數組。
- progressContexts: 進度回調函數綁定的上下文數組
- resolveContexts: 成功回調函數綁定的上下文數組
updateFn
updateFn = function(i, ctx, val){
return function(value){
ctx[i] = this
val[i] = arguments.length > 1 ? slice.call(arguments) : value
if (val === progressValues) {
deferred.notifyWith(ctx, val)
} else if (!(--remain)) {
deferred.resolveWith(ctx, val)
}
}
}
updateFn
方法,在每個異步對象執行 resolve
方法和 progress
方法時都調用。
參數 i
爲異步對象的索引值,參數 ctx
爲對應的上下文數組,即 resolveContexts
或 resolveContexts
, val
爲對應的回調函數數組,即 progresValues
或 resolveValues
。
if (val === progressValues) {
deferred.notifyWith(ctx, val)
}
如果爲 progress
的回調,則調用 deferred
的 notifyWith
方法。
else if (!(--remain)) {
deferred.resolveWith(ctx, val)
}
否則,將 remain
減少 1
,如果回調已經執行完畢,則調用 deferred
的 resolveWith
方法。
依次處理異步對象
if (len > 1) {
progressValues = new Array(len)
progressContexts = new Array(len)
resolveContexts = new Array(len)
for ( ; i < len; ++i ) {
if (resolveValues[i] && $.isFunction(resolveValues[i].promise)) {
resolveValues[i].promise()
.done(updateFn(i, resolveContexts, resolveValues))
.fail(deferred.reject)
.progress(updateFn(i, progressContexts, progressValues))
} else {
--remain
}
}
}
首先初始化 progressValues
、progressContexts
和 resolveContexts
,數組長度爲異步對象的長度。
if (resolveValues[i] && $.isFunction(resolveValues[i].promise)) {
resolveValues[i].promise()
.done(updateFn(i, resolveContexts, resolveValues))
.fail(deferred.reject)
.progress(updateFn(i, progressContexts, progressValues))
}
如果爲 promise
對象,則調用對應的 promise
方法。
else {
--remain
}
如果不是 promise
對象,則將 remian
減少 1
。
if (!remain) deferred.resolveWith(resolveContexts, resolveValues)
return deferred.promise()
如果無參數,或者參數不是異步對象,或者所有的參數列表都不是異步對象,則直接調用 resoveWith
方法,調用成功函數列表。
最後返回的是 promise
對象。
系列文章
- 讀Zepto源碼之代碼結構
- 讀 Zepto 源碼之內部方法
- 讀Zepto源碼之工具函數
- 讀Zepto源碼之神奇的$
- 讀Zepto源碼之集合操作
- 讀Zepto源碼之集合元素查找
- 讀Zepto源碼之操作DOM
- 讀Zepto源碼之樣式操作
- 讀Zepto源碼之屬性操作
- 讀Zepto源碼之Event模塊
- 讀Zepto源碼之IE模塊
- 讀Zepto源碼之Callbacks模塊
參考
License
最後,所有文章都會同步發送到微信公衆號上,歡迎關注,歡迎提意見:
作者:對角另一面