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介面用來監聽一個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等等。