一、何為異步
執行任務的過程可以被分為發起和執行兩個部分。
同步執行模式:任務發起后必須等待直到任務執行完成并返回結果后,才會執行下一個任務。
異步執行模式:任務發起后不等待任務執行完成,而是馬上執行下一個任務,當任務執行完成時則會收到通知。
面對IO操作頻繁的場景,異步執行模式可在同等的硬件資源條件下提供更大的并發處理能力,也就是更大的吞吐量。
但由于異步執行模式打破人們固有的思維方式,并且任務的發起和任務的執行是分離的,從而提高編程的復雜度。
多線程、多進程均可實現異步模式。
二、從回調地獄說起
相信大家都聽過“回調地獄”這一讓人蛋疼由難以避免的異步執行模式副作用。示例:
setTimeout(function(){ setTimeout(function(){ setTimeout(function(){ setTimeout(function(){ }, 1000) }, 1000) }, 1000) }, 1000)
由于JS是通過異步執行模式來實現多任務并發執行的,因此不可避免地會遇到異步任務連環嵌套的尷尬局面,而回調地獄則是異步任務嵌套的具體表現形式了。
回調地獄不僅造成代碼難以維護,并且會加大調試的難度,一言以蔽之——無法避免的蛋疼:(
三、那些舒緩Callback Hell的方案
既然回調地獄如此的不優雅但又無法避免,那么有沒有一些減輕痛楚的抽象方式來處理回調呢?
在尋找良藥之前,我們需要先了解的是形成回調地獄的原因,從局部看則是在發起異步任務前必須滿足某些前置條件,從全局看則是異步執行模式下的流程控制。其實在同步執行模式當中也存在同樣的情況,只不過同步執行模式與我們平常思考的方式一致,因此先滿足前置條件再執行同步任務則是順理成章的事情,也沒多大的感覺。但到了異步任務當中則成為突出的問題。想一想,如果異步任務A->異步任務B->異步任務C均以前一個異步任務為前置條件,那么它們的關系其實也就是同步執行,但代碼表達上卻被迫要使用異步編碼模式,這種內在關系與表現形式的差異就造就出著名的回調地獄了。
同步執行模式下的流程控制有 if...elseif...else 、 while 和 try...catch..finally 。而我們的終極目標是采用通過的方式來表達異步執行模式下的流程控制。顯然在不改變JS語法的情況下這注定是個偽命題。而我們能做的是不斷接近而已。
而@樸靈的EventProxy則是其中一個緩解回調函數之痛的工具庫。
EventProxy作為一個事件系統,通過after、tail等事件訂閱方法提供帶約束的事件觸發機制,“約束”對應“前置條件”,因此我們可以利用這種帶約束的事件觸發機制來作為異步執行模式下的流程控制表達方式。
例如,現在需要在任務A和任務B成功執行后才能執行任務C。
/* 同步執行模式 */ try{ var result4A = execA() var result4B = execB() var result4C = execC() } catch(e){} /* 異步執行模式 */ // 1. 回調函數方式 —— 出現Callback Hell了! execA(function(){ execB(function(){ execC() }) }) // 2. EventProxy var ep = EventProxy.create('a', 'b', execC) ep.fail(function $noop$(){}) execA(ep.done('a')) execB(ep.done('b'))
可以看到使用EventProxy時回調函數的數目并沒有減少,但回調地獄卻不見了(驗證了回調地獄不是由回調函數引起,而是由異步執行模式下的流程控制引起的)
但由于EventProxy采用事件機制來做流程控制,而事件機制好處是降低模塊的耦合度,但從另一個角度來說會使整個系統結構松散難以看出主干模塊,因此通過事件機制實現流程控制必然導致代碼結構松散和邏輯離散,不過這可以良好的組織形式來讓代碼結構更緊密一些。
四、認識Promise
這里的Promise指的是已經被ES6納入囊中的Promises/A+規范及其實現。使用示例:
var p = new Promise(function(resolve, reject){ resolve("test") }) p .then(function(val){ console.log(val) return val + 1 }, function(reason){ }) .then(function(val){ console.log(val) }, function(reason){ })
我是從jQuery.Deferred的promise方法那開始知道有Promise的存在。但Promises/A+到底描述的一個怎樣的機制呢?
1. 表象——API
Promises/A+中規定Promise狀態為pending(默認值)、fufilled或rejected,其中狀態僅能從pending->fulfilled或pending->rejected,并且可通過then和catch訂閱狀態變化事件。狀態變化事件的回調函數執行結果會影響Promise鏈中下一個Promise實例的狀態。另外在觸發Promise狀態變化時是可以攜帶附加信息的,并且該附加信息將沿著Promise鏈被一直傳遞下去直到被某個Promise的事件回調函數接收為止。而且Promise還提供Promise.all和Promise.race兩個幫助方法來實現與或的邏輯關系,提供Promsie.resolve來將thenable對象轉換為Promise對象。
2. 流程控制
通過Promise我們可以成功脫離回調地獄。如:
var execAP = Promise.resolve({then:execA}) , execBP = Promise.resolve({then:execB}) Promise .all(execAP, execBP) .then(execC)
這也是Promise被大家廣泛認識的功能。
3. 信任機制
由Labjs作者編寫的《深入理解Promise五部曲》從另一個角度對Promise進行更深刻的解讀。當我們需要通過第三方工具庫或接口來控制本地功能模塊時,則通過Promise建立一套信任機制,確保本地功能模塊在可預測的范圍內被第三方操控。
而Proimse僅作為庫開發者的樂高積木,面對普通開發者則需要提供更高層次的抽象。
五、認識Generator Function
Generator Function是ES6引入的新特性——生成器函數。通過組合Promise和Generator Function我們就可以實現采用通過的方式來表達異步執行模式下的流程控制了!!!
六、相關筆記
《JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源碼剖析》
《JS魔法堂:ES6新特性——GeneratorFunction介紹》
《JS魔法堂: Native Promise Only源碼剖析》
七、iPromise
iPromise是我邊學異步處理邊開發的Promises/A+規范的實現,并且內部已實現了對Generator Function的支持。經過3次全局重構后現處于v0.8.2,我覺得現在的代碼結構閱讀起來比較流暢,并且API已固定,預計日后就是打打補丁罷了。歡迎大家fork來玩玩 iPromise@github
八、總結
本文為這段時間我對《JavaScript框架設計》——第12章 異步處理的學習和實踐匯總,若有紕漏和不足之處請大家指正、補充,謝謝!
尊重原創,轉載請注明來自:http://www.cnblogs.com/fsjohnhuang/p/4296831.html ^_^肥仔John
文章列表