讀Zepto源碼之Deferred模塊

Deferred 模塊也不是必備的模塊,可是 ajax 模塊中,要用到 promise 風格,必需引入 Deferred 模塊。Deferred 也用到了上一篇文章《讀Zepto源碼之Callbacks模塊》介紹的 Callbacks 模塊。javascript

讀 Zepto 源碼系列文章已經放到了github上,歡迎star: reading-zeptohtml

源碼版本

本文閱讀的源碼爲 zepto1.2.0java

Promise/A+ 規範

規範的具體內容能夠參考《Promises/A+》 和對應的中文翻譯 《Promise/A+規範》,這裏只簡單總結一下。jquery

promise 是一個包含兼容 promise 規範的函數或對象,promise 包含三種狀態 pending 進行中、fulfilled 已完成, rejected 被拒絕,而且必須處於其中一種狀態。git

pending 狀態能夠轉換成 fulfilled 狀態或者 rejected 狀態,可是 fulfilled 狀態和 rejected 狀態不能再轉換成其餘狀態。github

promise 必須包含 then 方法,then 方法能夠接收兩個參數,參數類型都爲函數,分別爲狀態變爲 fulfilled 後調用的 onFulfilled 函數和 rejected 後調用的 onRejected 函數。ajax

大體瞭解 Promise/A+ 規範後,對後面源碼的閱讀會有幫助。segmentfault

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 包含執行方法 alwaysthendonefailprogress 和輔助方法 statepromise
  • deferred: deferred 除了繼承 promise 的方法外,還增長了切換方法, resolveresoveWithrejectrejectWithnotifynotifyWith 。 ​

done、fail和progress的生成

$.each(tuples, function(i, tuple){
  ...
})

方法的生成,經過遍歷 tuples 實現promise

var list = tuple[2],
	stateString = tuple[3]

promise[tuple[1]] = list.add

list 是工廠方法 $.Callbacks 生成的管理回調函數的一系列方法。具體參見上一篇文章《讀Zepto源碼之Callbacks模塊》。注意,tuples 的全部項中的 $Callbacks 都配置了 memory:1 ,即開啓記憶模式,增長的方法都會當即觸發。包含 resovereject 的項都傳遞了 once:1 ,即回調列表只能觸發一次。

stateString 是狀態描述,只有包含了 resolvereject 的數組項才具備。

index1 的項,取出來的分別爲 donefailprogress ,因此 promise 上的 donefailprogress 方法,調用的是 Callbacks 中的 add 方法,實質是往各自的回調列表中添加回調函數。

狀態切換

if (stateString) {
  list.add(function(){
    state = stateString
  }, tuples[i^1][2].disable, tuples[2][2].lock)
}

若是 stateString 存在,即包含 resolvereject 的數組項,則往對應的回調列表中添加切換 state 狀態的方法,將 state 更改成對應方法觸發後的狀態。

同時,將狀態鎖定,即狀態變爲 resolvedrejected 狀態後,不能再更改成其餘狀態。這裏用來按位異或運算符 ^ 來實現。當 i0 ,即狀態變爲 resolved 時, i^11tuples[i^1][2].disablerejected 的回調列表禁用,當 i1 時, i^10 ,將 resolved 的回調列表禁用。即實現了成功和失敗的狀態互斥,作得狀態鎖定,不能再更改。

在狀態變動後,同時將 tuples[2] 的回調列表鎖定,要注意 disablelock 的區別,具體見《讀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 對象。

resolveWithrejectWithnotifyWith 方法,其實等價於 CallbackfireWith 方法,fireWith 方法的第一個參數爲上下文對象。

從源碼中能夠看到 resolverejectnotify 方法,調用的是對應的 With 後綴方法,若是當前上下文爲 deferred 對象,則傳入 promise 對象做爲上下文。

promise 對象

.state()

state: function() {
  return state
},

state 方法的做用是返回當前的狀態。

.always()

always: function() {
  deferred.done(arguments).fail(arguments)
  return this
},

always 是一種省事的寫法,即不管成功仍是失敗,都會執行回調。調用的是 deferred 上的 donefail 方法。或許你會有疑惑,donefail 方法,上面的分析中,明明是 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()
}

promisethen 方法接收三個參數,分別爲成功的回調、失敗的回調和進度的回調。

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

這裏的變量 fnsthen 所傳入的參數,即上文提到的三個回調。

最後的 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)
    }
  })
})

遍歷 tuplestuples 中的順序,跟 then 中規定 donefailprogress 的回調順序一致。

因此用 var fn = $.isFunction(fns[i]) && fns[i] 來判斷對應位置的參數是否爲 function 類型,若是是,則賦值給 fn

deferred[tuple[1]] 是對應的是 donefailprogress 。因此在 then 裏,會依次執行這三個方法。

var returned = fn && fn.apply(this, arguments)

returnedthen 中三個回調方法執行後返回的結果。

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 對象上並無donefailprogress 方法,這是從 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 對象,若是隻有一個異步對象(只有一個參數,而且不爲異步對象時, remain0 ),則直接使用當前的 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 爲對應的上下文數組,即 resolveContextsresolveContextsval 爲對應的回調函數數組,即 progresValuesresolveValues

if (val === progressValues) {
	deferred.notifyWith(ctx, val)
}

若是爲 progress 的回調,則調用 deferrednotifyWith 方法。

else if (!(--remain)) {
  deferred.resolveWith(ctx, val)
}

不然,將 remain 減小 1,若是回調已經執行完畢,則調用 deferredresolveWith 方法。

依次處理異步對象

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
    }
  }
}

首先初始化 progressValuesprogressContextsresolveContexts ,數組長度爲異步對象的長度。

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 對象。

系列文章

  1. 讀Zepto源碼之代碼結構
  2. 讀 Zepto 源碼以內部方法
  3. 讀Zepto源碼之工具函數
  4. 讀Zepto源碼之神奇的$
  5. 讀Zepto源碼之集合操做
  6. 讀Zepto源碼之集合元素查找
  7. 讀Zepto源碼之操做DOM
  8. 讀Zepto源碼之樣式操做
  9. 讀Zepto源碼之屬性操做
  10. 讀Zepto源碼之Event模塊
  11. 讀Zepto源碼之IE模塊
  12. 讀Zepto源碼之Callbacks模塊

參考

License

License: CC BY-NC-ND 4.0

最後,全部文章都會同步發送到微信公衆號上,歡迎關注,歡迎提意見:

做者:對角另外一面

相關文章
相關標籤/搜索