最近懶癌發做,說好的系列文章,寫了一半,一直懶得寫,今天補上一篇。segmentfault
Deferred
咱們在使用promise
對象時,總會提到一個與它關係密切的對象——Deferred
。其實Deferred
沒什麼內容可講的,其實很簡單。數組
它包含一個promise
對象promise
它能夠改變對應的promise
的狀態app
簡單的實現以下:ide
class Deferred{ constructor(){ let defer = {}; defer.promise = new Promise((resolve, reject)=>{ defer.resolve = resolve; defer.reject = reject; }) return defer; } }
咱們知道promise
對象內部的狀態,自己是在建立對象時傳入的函數內控制,外部是訪問不到的,Deferred
對象在它的基礎上包裝了一層,並提供了兩個在外部改變它狀態的方法。函數
用法其實在Promise介紹--規範篇中的例子內,多處使用到了,這裏就再也不贅述。總以爲文章寫這麼點兒,就顯得太水了。。因此藉此,講講jQuery
中的實現。this
jQuery
中還有一個靜態方法$.Callbacks()
,因爲$.Deferred()
強依賴它,因此咱們先從它開刀。spa
$.Callbacks()
$.Callbacks()
咱們稱爲回調對象,它內部會維護一個數組,咱們能夠向其中添加若干個回調函數,而後在某一條件下觸發執行。code
有幾個方法從名字咱們就知道它的做用是什麼,add
向數組內部添加一個回調函數,empty
清空數組,fire
觸發回調函數,has
數組中是否已經添加某回調函數,remove
從數組中刪除某回調函數。對象
fireWith
函數接收兩個參數,第一個是回調函數執行的上下文,第二個是回傳給回調函數的參數。fire
中其實內部調用的就是fireWith
,其中第一個參數傳遞的是this
。
其它的幾個函數,都和回調數組的狀態有關。建立Callbacks
對象時,接收一個字符串或者對象做爲參數。其實內部都會轉換爲對象,這裏不贅述,不一樣字符串表示不一樣的處理方式,一一介紹。
once
:對象只會調用一次。
let cb = $.Callbacks('once') function a(){console.log('a')} function b(){console.log('b')} cb.add(a) cb.fire() cb.add(b) cb.fire() // a
第一次fire
以後,回調列表以後不會再次觸發。
memory
: 記住回調列表的執行狀態,若是回調函數fire
過一次,以後每次add
以後,則自動觸發該回調。
let cb = $.Callbacks('memory') function a(){console.log('a')} function b(){console.log('b')} cb.add(a) cb.fire() // a cb.add(b) // b
第一次fire
以後,再次add
新的回調函數b
時,自動執行回調b
。
unique
:每個回調函數只能夠添加一次。
let cb = $.Callbacks('unique') function a(){console.log('a')} function b(){console.log('b')} cb.add(a) cb.add(a) cb.fire() // a cb.add(b) cb.fire() // a // b
第一次fire
時,只會打印一個a
,說明第二個a
沒有添加成功,但當咱們添加b
時,是能夠添加成功的。
stopOnFalse
:當前面的回調函數返回false
時,終止後面的回調繼續執行。
let cb = $.Callbacks('stopOnFalse') function a(){console.log('a');return false;} function b(){console.log('b')} cb.add(a) cb.add(b) cb.fire() // a
函數a
返回了false
,致使函數b
沒有執行。
咱們再回過頭看$.Callbacks()
對象的方法,lock
方法表示鎖住回調數組,再也不執行,也就是模式爲once
時,調用一次fire
後的狀態,即在此以後不能夠在此觸發。以下:
let cb = $.Callbacks() function a(){console.log('a')} function b(){console.log('b')} cb.add(a) cb.fire() cb.lock() cb.add(b) cb.fire()
lock
以後,再次添加函數b
並調用fire
時,不會再次執行,與once
模式下效果相似。但若是是memory
模式,回調先fire
,而後再lock
,以後再次add
時,新添加的函數依然會執行。
let cb = $.Callbacks('memory') function a(){console.log('a')} function b(){console.log('b')} cb.add(a) cb.fire() cb.lock() cb.add(b) // a // b
其實這種效果和直接建立回調對象時,參數設爲once memory
是一致的。也就是說,以下代碼與上面效果一致。
let cb = $.Callbacks('once memory') function a(){console.log('a')} function b(){console.log('b')} cb.add(a) cb.fire() cb.add(b) // a // b
咱們發現這種once memory
模式,正好與Promise
中then
方法添加的回調很相似。若是promise
對象處於pending
狀態,則then
方法添加的回調存儲在一個數組中,當promise
對象狀態改變(fire
)時,執行相應的回調,且以後再次經過then
方法添加回調函數,新回調會馬上執行。同時,每個回調只能執行一次。因此,$.Deferred()
內部用的正好是once memory
的Callbacks
。
還有一個函數叫作disable
,它的做用是直接禁用掉這個回調對象,清空回調數組,禁掉fire
、add
等。
locked
用於判斷數組是否被鎖住,返回true
或false
。disabled
用於判斷回調對象是否被警用,一樣返回true
或false
。
$.Deferred()
有了以上的基礎,咱們接下來看看jQuery
中,Deferred
對象的實現。咱們先看看它都有哪些方法。如圖:
別的方法咱們暫且不關注,咱們注意到裏面有四個咱們比較熟悉的方法,promise
,reject
,resolve
。它們和咱們前面說的Deferred
對象中做用差很少。
jQuery
的Deferred
中有三個添加回調函數的方法done
,fail
,progress
,分別對應添加promise
狀態爲resolved
、rejected
和pending
時的回調,同時對應有三個觸發回調函數的方法resolve
、reject
和notify
。
咱們接下來看看它內部是怎麼實現的。首先爲每種狀態分別建立一個Callbacks
對象,以下:
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")] ]
咱們發現done
、fail
對應的回調對象是once memory
,而progress
對應的是memory
。說明經過progress
添加的函數,能夠屢次重複調用。
而後定義了一個state
用來保存狀態,以及內部的一個promise
對象。
state = "pending", promise = { state: function() { return state; }, always: function() { deferred.done(arguments).fail(arguments); return this; }, then: function( /* fnDone, fnFail, fnProgress */ ) { }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function(obj) { return obj != null ? jQuery.extend(obj, promise) : promise; } },
接下來會執行一個循環,以下:
// Add list-specific methods jQuery.each(tuples, function(i, tuple) { var list = tuple[2], stateString = tuple[3]; // promise[ done | fail | progress ] = list.add promise[tuple[1]] = list.add; // Handle state if (stateString) { list.add(function() { // state = [ resolved | rejected ] state = stateString; // [ reject_list | resolve_list ].disable; progress_list.lock }, tuples[i ^ 1][4].disable, tuples[2][5].lock); } // deferred[ resolve | reject | notify ] deferred[tuple[0]] = function() { deferred[tuple[0] + "With"](this === deferred ? promise : this, arguments); return this; }; deferred[tuple[0] + "With"] = list.fireWith; });
這一段代碼中咱們能夠看出,done
、fail
、progress
其實就是add
,resolve
、reject
和notify
其實就是fire
,與之對應的resolveWith
、rejectWith
和notifyWith
其實就是fireWith
。且成功和失敗的回調數組中,會預先添加一個函數,用來設置promise
的狀態和禁用掉其它狀態下的回調對象。
從上面這段代碼中咱們也能夠看出,添加回調函數的方法,都是添加在promise
對象上的,而觸發回調的方法是添加在deferred
對象上的。代碼中會經過以下方法,把promise
對象的方法合併到deferred
對象上。
promise.promise(deferred);
因此,咱們打印一下$.Deferred().promise()
。
發現它確實比$.Deferred()
少了那幾個觸發回調的方法。
其它的幾個方法咱們簡單說一下,always
會同時在成功和失敗的回調數組中添加方法。state
是查看當前promise
對象的狀態。
then
方法以下:
then: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments; return jQuery.Deferred(function(newDefer) { jQuery.each(tuples, function(i, tuple) { var fn = jQuery.isFunction(fns[i]) && fns[i]; // deferred[ done | fail | progress ] for forwarding actions to newDefer deferred[tuple[1]](function() { var returned = fn && fn.apply(this, arguments); if (returned && jQuery.isFunction(returned.promise)) { returned.promise() .progress(newDefer.notify) .done(newDefer.resolve) .fail(newDefer.reject); } else { newDefer[tuple[0] + "With"]( this === promise ? newDefer.promise() : this, fn ? [returned] : arguments ); } }); }); fns = null; }).promise(); }
從代碼中咱們能夠看出,then
方法和規範中的then
方法相似,不過這裏多了第三個參數,是用於給progress
添加回調函數,同時返回一個新的promise
對象。
pipe
是爲了向前兼容,它與then
是相等的。
$.when()
jQuery
中還有一個與之相關的方法$.when()
,它的做用相似於Promise.all()
。具體實現方式基本是新建了一個Deferred
對象,而後遍歷全部傳遞進去的promise
對象。不過添加了progres
的處理。總之和規範有不少的不一樣,你們有興趣的就本身看一下吧。