姊妹篇 移動web app開發必備 - 異步隊列 Deferred
在分析Deferred之前我覺得還是有必要把老套的設計模式給搬出來,便於理解源碼!
觀察者模式
觀察者模式( 又叫發布者-訂閱者模式 )應該是最常用的模式之一.
它定義了一種一對多的關系讓多個觀察者對象同時監聽某一個主題對象,這個主題對象的狀態發生變化時就會通知所有的觀察者對象,使得它們能夠自動更新自己。
使用觀察者模式的好處:
- 支持簡單的廣播通信,自動通知所有已經訂閱過的對象。
- 頁面載入后目標對象很容易與觀察者存在一種動態關聯,增加了靈活性。
- 目標對象與觀察者之間的抽象耦合關系能夠單獨擴展以及重用。
- 在這種模式中,有兩類對象,分別是“觀察者-Observer”和“目標對象-Subject”。
- 目標對象中保存着一份觀察者的列表,當目標對象的狀態發生改變的時候就主動向觀察者發出通知(調用觀察者提供的方法),從而建立一種發布/訂閱的關系。
- 這一種發布/訂閱的關系常用於實現事件、消息的處理系統。
觀察者模式類圖
參與者
- Subject(抽象主題)
- 能夠知道它自己的觀察者,若干觀察者對象可能監視一個主題對象;
- 提供一個接口,用來附加和取消觀察者對象;
- Observer(抽象觀察者)
- 它為對象定義了一個(自我)更新的接口,並且當主題對象發生改變的時候能夠被通知;
- ConcreteSubject(具體主題)
- 存儲具體觀察者感興趣的狀態;
- 當它的狀態改變的時候,通知它所有的觀察者對象;
- ConcreteObserver(具體觀察者)
- 持有一個具體主題的引用;
- 存儲和主題對象一致的狀態,並且該能夠保留該狀態;
- 實現抽象觀察者的Update接口,以便能夠同主題對象的狀態保持一致;
工作方式
- 當某個具體主題狀態發生改變的時候,通知它的所有觀察者對象,以便保證這些觀察者對象的狀態同它的狀態保持一致;
- 當被告知具體主題對象發生改變,一個具體觀察者對象可能會去查詢主題對象,以便獲得一個消息;該觀察者使用這個消息來使它的狀態同主題對象的狀態保持一致;
觀察者模式序列圖
觀察者對象初始化變化請求,它不會立即更新,直到通過主題對象的Notify方法來獲得一個變化的通知。主題的Notify方法並非總是被主題對象調用,它也能夠被觀察者對象調用,或者完全被其他不同類型的對象調用。
Deferred怎樣工作
- 可以創建一個Deferred對象直接,但你通常請求從一個數據源
- 數據源是一個函數,返回一個延遲對象
- 延遲對象提供了訪問數據源的數據,允許你把成功回調函數/或失敗函數其回調鏈
- 當數據源有結果,它將在延時對象上調用resolve(result)方法或者在失敗的情況下調用reject(failure)方法,這導致延遲對象的回調鏈被釋放了——這意味着每個鏈接鏈(回調或errback)被稱為反過來,結果是輸入到第一個回調,它的輸出是輸入到下一個回調(等等)
- 如果一個callback (or errback) 返回一個 Failure對象,那么下一個就是errback調用,是否就是callback調用
Deferred整體結構
Deferred的定義:
- Deferred是基於Promises/A,Promises是一種異步編程模型,通過一組API來規范化異步操作,這樣也能夠讓異步操作的流程控制更加容易
- 由於Promises對於新手而言理解曲線還是比較陡峭的,這里循序漸進的給大家介紹,同時實現一個最簡單的Promises/A代碼
- Promises/A有個別名叫做“thenable”,就是“可以then”的。這里一個promise有三種狀態:[默認、完成、失敗],初始創建的時候是默認狀態,狀態只可以從默認變成完成,或者默認變成失敗。一旦完成或者失敗,狀態就不能再變定義的接口
Deferred提供的API
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不清晰,內部還有很多接口的實現沒有說明
內部實現的處理器就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 }
運行流程
- 其中d就是傳入的異步對象了
- d.callbacks就是訂閱收集的具體觀察者感興趣的內容,也就是callback or errback
- 匹配出正確的回調方法,執行
- d.result = fn(d.result) 傳入回調函數需要的參數
- 單一的流程就執行完畢了
我們看看復雜點的構造
案例分析二
等待許多異步數據源
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。
- 用起來是不是很爽!
考慮這章拉的太長了,大家看的會有點小悶,所以下一節就開始隊列的源碼分析了