Javascript裡異步編程逐漸被大家接受,先前大家一般通過回調嵌套,setTimeout、setInterval等方式實現,代碼看起來非常不直觀,不看整個代碼邏輯很難快速理解。Javascript裡異步函數大概有I/O函數(Ajax、postMessage、img load、script load等)、計時函數(setTimeout、setInterval)等。
這些我們都很熟悉,在復雜的應用中往往會嵌套多層,甚至以為某些步驟未完成而導致程序異常,最簡單的例子:比如你往DOM中注入節點,你必須等待節點注入後在操作這個節點,當大量節點注入的時候,時間往往很難把握。如果我們得代碼依賴第三方api的數據。我們無法獲悉一個API響應的延遲時間,應用程序的其他部分可能會被阻塞,直到它返回結果。Promises對這個問題提供了一個更好的解決方案,它是非阻塞的,並且與代碼完全解耦 。
那麼,我看看Javascript裡異步編程,首先推薦大家看看相對來說比較流行的Promises/A規范。
Promises/A規范
注:為了便於理解,描述可能和Promises/A規范有所出入;
CommonJS之Promises/A規范,通過規范API接口來簡化異步編程,使我們的異步邏輯代碼更易理解。
遵循Promises/A規范的實現我們稱之為Promise對象,Promise對象有且僅有三種狀態:unfulfilled(未完成)、fulfilled(已完成)、failed(失敗/拒絕);初始創建的時候是unfulfilled(未完成)狀態,狀態只可以從unfulfilled(未完成)變成fulfilled(已完成),或者unfulfilled(未完成)變成failed(失敗/拒絕)。狀態一旦變成fulfilled(已完成)或者failed(失敗/拒絕),狀態就不能再變了。
Promises/A規范提供了一個在程序中描述延時(或將來)概念的解決方案。主要的思想不是執行一個方法然後阻塞應用程序等待結果返回後再回調其他方法,而是返回一個Promise對象來滿足未來監聽。fulfilled狀態和failed狀態都可以被監聽。Promise通過實現一個then接口來返回Promise對象來注冊回調:
. 代碼如下:then(fulfilledHandler, errorHandler, progressHandler);
then接口用於監聽一個Promise的不同狀態。fulfilledHandler用於監聽fulfilled(已完成)狀態,errorHandler用於監聽failed(失敗/拒絕)狀態,progressHandler用於監聽unfulfilled(未完成)狀態。Promise不強制實現unfulfilled(未完成)的事件監聽(例如我們知道舊版本的jQuery(1.5,1.6)的Deferred就是一個Promise的實現,但沒有實現對unfulfilled(未完成)狀態的監聽來回調progressHandler)。
一般認為,then接口返回的是一個新的Promise對象,而不是原來的Promise對象,這個新的新的Promise對象可以理解為是原來Promise對象的一個視圖,它只包含原有Promise對象的一組方法,這些方法只能觀察原有Promise對象的狀態,而無法更改deferred對象的內在狀態。這樣可以避免多個調用者之間的沖突,多個調用者可以通過改變新的Promise對象狀態而不影響別的調用者。
另外,Promise提供了resolve(實現狀態由未完成到已完成)和reject(實現狀態由未完成到拒絕或失敗)兩個接口實現狀態的轉變。
發一張圖片幫助理解一下:
有了Promise,就可以以同步的思維去編寫異步的邏輯了。在異步函數裡,不能使用try/catch捕獲異常,也不能拋出異常。有了Promise,我們可以直接顯式定義errorHandler,相當於捕獲異常。
以下是幾個遵循Promises/A規范的類庫,when,q,rsvp.js,jQuery.Deferred等等。