移動web app開發必備 - Deferred 源碼分析

姊妹篇  移動web app開發必備 - 異步隊列 Deferredhtml

在分析Deferred以前我以爲仍是有必要把老套的設計模式給搬出來,便於理解源碼!jquery

觀察者模式

觀察者模式( 又叫發佈者-訂閱者模式 )應該是最經常使用的模式之一.web

它定義了一種一對多的關係讓多個觀察者對象同時監聽某一個主題對象,這個主題對象的狀態發生變化時就會通知全部的觀察者對象,使得它們可以自動更新本身。編程

使用觀察者模式的好處:設計模式

  1. 支持簡單的廣播通訊,自動通知全部已經訂閱過的對象。
  2. 頁面載入後目標對象很容易與觀察者存在一種動態關聯,增長了靈活性。
  3. 目標對象與觀察者之間的抽象耦合關係可以單獨擴展以及重用。
  • 在這種模式中,有兩類對象,分別是「觀察者-Observer」和「目標對象-Subject」。
  • 目標對象中保存着一份觀察者的列表,當目標對象的狀態發生改變的時候就主動向觀察者發出通知(調用觀察者提供的方法),從而創建一種發佈/訂閱的關係。
  • 這一種發佈/訂閱的關係經常使用於實現事件、消息的處理系統。

觀察者模式類圖

image

參與者

  • Subject(抽象主題)
  • 可以知道它本身的觀察者,若干觀察者對象可能監視一個主題對象;
  • 提供一個接口,用來附加和取消觀察者對象;
  • Observer(抽象觀察者)
  • 它爲對象定義了一個(自我)更新的接口,而且當主題對象發生改變的時候可以被通知;
  • ConcreteSubject(具體主題)
  • 存儲具體觀察者感興趣的狀態;
  • 當它的狀態改變的時候,通知它全部的觀察者對象;
  • ConcreteObserver(具體觀察者)
  • 持有一個具體主題的引用;
  • 存儲和主題對象一致的狀態,而且該可以保留該狀態;
  • 實現抽象觀察者的Update接口,以便可以同主題對象的狀態保持一致;

工做方式

  • 當某個具體主題狀態發生改變的時候,通知它的全部觀察者對象,以便保證這些觀察者對象的狀態同它的狀態保持一致;
  • 當被告知具體主題對象發生改變,一個具體觀察者對象可能會去查詢主題對象,以便得到一個消息;該觀察者使用這個消息來使它的狀態同主題對象的狀態保持一致;

觀察者模式序列圖

image

觀察者對象初始化變化請求,它不會當即更新,直到經過主題對象的Notify方法來得到一個變化的通知。主題的Notify方法並不是老是被主題對象調用,它也可以被觀察者對象調用,或者徹底被其餘不一樣類型的對象調用。數組

Deferred怎樣工做

  • 能夠建立一個Deferred對象直接,但你一般請求從一個數據源
  • 數據源是一個函數,返回一個延遲對象
  • 延遲對象提供了訪問數據源的數據,容許你把成功回調函數/或失敗函數其回調鏈
  • 當數據源有結果,它將在延時對象上調用resolve(result)方法或者在失敗的狀況下調用reject(failure)方法,這致使延遲對象的回調鏈被釋放了——這意味着每一個連接鏈(回調或errback)被稱爲反過來,結果是輸入到第一個回調,它的輸出是輸入到下一個回調(等等)
  • 若是一個callback (or errback) 返回一個 Failure對象,那麼下一個就是errback調用,是否就是callback調用

Deferred-process

 

Deferred總體結構

Deferred的定義:promise

  • Deferred是基於Promises/A,Promises是一種異步編程模型,經過一組API來規範化異步操做,這樣也可以讓異步操做的流程控制更加容易
  • 因爲Promises對於新手而言理解曲線仍是比較陡峭的,這裏按部就班的給你們介紹,同時實現一個最簡單的Promises/A代碼
  • Promises/A有個別名叫作「thenable」,就是「能夠then」的。這裏一個promise有三種狀態:[默認、完成、失敗],初始建立的時候是默認狀態,狀態只能夠從默認變成完成,或者默認變成失敗。一旦完成或者失敗,狀態就不能再變定義的接口

Deferred提供的APIapp

  var DeferredAPI = {
    deferred     : deferred,
    all          : all,
    Deferred     : Deferred,
    DeferredList : DeferredList,
    wrapResult   : wrapResult,
    wrapFailure  : wrapFailure,
    Failure      : Failure
  }

案例分析一

咱們經過簡單的demo來解析程序的執行流程異步

            function asynchronous(delay,name) {
                var d = new deferred.Deferred()
                setTimeout(function() {
                     d.resolve('執行'+name)
                }, delay || 1000);
                return d
            };
            var d1 = new asynchronous(1000, 'd1');
            d1.then(function(result){
                alert(result) //結果是 執行d1
            })
  • 執行asynchronous方法,傳入參數
  • new deferred.Deferred() 用來構造一個Deferred對象
  • setTimeout 模擬異步執行操做
  • d.resolved表示該操做成功完成了
  • 返回Deferred對象
  • then 增長成功回調函數和失敗回調函數到各自的隊列中便捷方法,兩個參數能夠是數組或null
  • setTimeout 執行完畢後出發resolved方法
  • 執行then註冊的回調函數
  • 執行結束

源碼實現

經過查看源碼就能發現,其實Deferred.js的官方API不清晰,內部還有不少接口的實現沒有說明async

內部實現的處理器就2個模塊,分別是:

  • 處理單個異步 Deferred 類
  • 處理多個異步 DeferredList 類

 

Deferred 類

 new deferred.Deferred()   
 deferred.deferred()

二着是等同的,最終的實現是Deferred類,每次實例化一次就是一個新是上下文

Deferred在構造以後,相對的實例就具備瞭如下方法:

  • cancel
  • then
  • fail
  • both
  • resolve
  • reject
  • pause
  • unpause
  • inspect
  • thenReturn
  • thenCall
  • failReturn
  • failCall

Deferred.then方法

來,訂閱一個
    Deferred.prototype.then = function (callback, errback) {
        this.callbacks.push({callback: callback, errback: errback})
        if (this.called) _run(this)
        return this
    }

能夠直接傳入二個回調函數,分別對應都 done|fail 二個狀態的回調, 跟 $.jquery仍是有區別,能夠直接傳入三個回調函數,分別對應done|fail|progress三個狀態的回調 

用this.callbakcs 存儲具體觀察者感興趣的內容

Deferred.resolve方法

發佈通知
  Deferred.prototype.resolve = function(result) {
      _startRun(this, result)
      return this
  }

代碼中間有不少邏輯判斷,咱們暫且先跳過,看看最終的執行方法

 1   function _run(d) {
 2     if (d.running) return
 3     var link, status, fn
 4     if (d.pauseCount > 0) return
 5       
 6     while (d.callbacks.length > 0) {
 7       link = d.callbacks.shift()
 8       status = (d.result instanceof Failure) ? 'errback' : 'callback'
 9       fn = link[status]
10       if (typeof fn !== 'function') continue
11       try {
12         d.running = true
13         d.result = fn(d.result)
14         d.running = false
15         if (d.result instanceof Deferred) {
16           d.pause()
17           _nest(d)
18           return
19         }
20       } catch (e) {
21         if (Deferred.consumeThrownExceptions) {
22           d.running = false
23           var f = new Failure(e)
24           f.source = f.source || status
25           d.result = f
26           if (d.verbose) {
27             console.warn('uncaught error in deferred ' + status + ': ' + e.message)
28             console.warn('Stack: ' + e.stack)
29           }
30         } else {
31           throw e
32         }
33       }
34     }
35   }
View Code

運行流程

  1. 其中d就是傳入的異步對象了
  2. d.callbacks就是訂閱收集的具體觀察者感興趣的內容,也就是callback or errback
  3. 匹配出正確的回調方法,執行
  4. d.result = fn(d.result) 傳入回調函數須要的參數
  5. 單一的流程就執行完畢了

 

咱們看看複雜點的構造

案例分析二

等待許多異步數據源

            function asynchronous(delay,name) {
                var d = new deferred.Deferred()
                setTimeout(function() {
                     d.resolve('執行'+name)
                }, delay || 1000);
                return d
            };

            var d1 = new asynchronous(2000, 'd1');
            var d2 = new asynchronous(3000, 'd2');

            deferred.all([d1, d2]).then(function(result){
                 console.log(result)   //["執行d1", "執行d2"]
            }).thenCall(console.log(11111));

執行流程:

  • 等待在一個列表的全部值的遞延的對象,或者開始一羣延遲操做和運行回調鏈當第一個成功仍是失敗了
  • 等待d1, d2都加載完畢後接受到全部的values後,deferred.all運行做用域鏈,固然也同樣能夠提供成功或者失敗
  • 返回的result將會是數組格式的 convert ['a', 'b', 'c'] to 'abc'

或者傳入配置參數:

fireOnFirstResult: true  只要返回第一個d1處理

fireOnFirstError: true   只返回第一個錯誤的處理

            deferred.all([d1, d2],{fireOnFirstResult: true}).then(function(result){
                 console.log(result)   //["執行d1"]
            }).thenCall(console.log(11111));

最後看2個API沒有給出的接口

dAll.failCall(console.error)
dAll.then(join).thenCall(console.log)

案例分析三

任意組合模式

            function asynchronous(delay,name) {
                var d = new deferred.Deferred()
                setTimeout(function() {
                     d.resolve('執行'+name)
                }, delay || 1000);
                return d
            };

            var d1 = new asynchronous(2000, 'd1');

           var d = d1.then(function(result1) {
               var d2 = new asynchronous(200, 'd2');
               return d2.then(function(result2) {
                   return  result
               })
           }).then(function(data) {
               console.log(data)
               return data
           })

執行流程:

  • 一個回調函數的返回值被傳遞到下一個回調。
  • 當一個回調返回一個延遲對象,原來的延遲將透明地等待其餘接收它的值,而後運行它本身的回調鏈使用該值。咱們把這種叫作嵌套。

總結:

  • 如今咱們能夠用deferred對象作不少事了,返回它,將它傳遞給另外一個函數,等。
  • 隨後的回調註冊在deferred將收到返回的值從最內層的回調:result1+ result2。
  • 用起來是否是很爽!

考慮這章拉的太長了,你們看的會有點小悶,因此下一節就開始隊列的源碼分析了

相關文章
相關標籤/搜索