天天看點

JavaScript進階之路——認識和使用Promise,重構你的Js代碼

  一轉眼,這2015年上半年就過去了,差不多一個月沒有寫部落格了,"罪過罪過"啊~~。進入了七月份,也就意味着我們上半年苦逼的單身生活結束了,從此刻起,我們要打起十二分的精神,開始下半年的單身生活。大家一起加油~~

  一直以來,JavaScript處理異步都是以callback的方式,在前端開發領域callback機制幾乎深入人心。在設計API的時候,不管是浏覽器廠商還是SDK開發商亦或是各種類庫的作者,基本上都已經遵循着callback的套路。近幾年随着JavaScript開發模式的逐漸成熟,CommonJS規範順勢而生,其中就包括提出了Promise規範,Promise完全改變了js異步程式設計的寫法,讓異步程式設計變得十分的易于了解。今天我們就來了解一下Promise~~

 一 、初識Promise

1、什麼是promise?

  Promise可能大家都不陌生,因為Promise規範已經出來好一段時間了,同時Promise也已經納入了ES6,而且高版本的chrome、firefox浏覽器都已經原生實作了Promise,隻不過和現如今流行的類Promise類庫相比少些API。

  所謂Promise,字面上可以了解為“承諾”,就是說A調用B,B傳回一個“承諾”給A,然後A就可以在寫計劃的時候這麼寫:當B傳回結果給我的時候,A執行方案S1,反之如果B因為什麼原因沒有給到A想要的結果,那麼A執行應急方案S2,這樣一來,所有的潛在風險都在A的可控範圍之内了。

Promise規範如下:

一個promise可能有三種狀态:等待(pending)、已完成(fulfilled)、已拒絕(rejected)

一個promise的狀态隻可能從“等待”轉到“完成”态或者“拒絕”态,不能逆向轉換,同時“完成”态和“拒絕”态不能互相轉換

promise必須實作<code>then</code>方法(可以說,then就是promise的核心),而且then必須傳回一個promise,同一個promise的then可以調用多次,并且回調的執行順序跟它們被定義時的順序一緻

then方法接受兩個參數,第一個參數是成功時的回調,在promise由“等待”态轉換到“完成”态時調用,另一個是失敗時的回調,在promise由“等待”态轉換到“拒絕”态時調用。同時,then可以接受另一個promise傳入,也接受一個“類then”的對象或方法,即thenable對象。

2.promise原理分析

  可以看到promise的規範并不是很多,下面我們一邊分析promise一邊自己寫一個promise的實作。Promise實作的大緻思路如下:

  構造函數Promise接受一個函數<code>resolver</code>,可以了解為傳入一個異步任務,resolver接受兩個參數,一個是成功時的回調,一個是失敗時的回調,這兩參數和通過then傳入的參數是對等的。

其次是then的實作,由于Promise要求then必須傳回一個promise,是以在then調用的時候會新生成一個promise,挂在目前promise的_next上,同一個promise多次調用都隻會傳回之前生成的_next。

由于then方法接受的兩個參數都是可選的,而且類型也沒限制,可以是函數,也可以是一個具體的值,還可以是另一個promise。下面是then的具體實作:

這裡,then做了簡化,其他promise類庫的實作比這個要複雜得多,同時功能也更多,比如還有第三個參數——notify,表示promise目前的進度,這在設計檔案上傳等時很有用。對then的各種參數的處理是最複雜的部分,有興趣的同學可以參看其他類Promise庫的實作。

在then的基礎上,應該還需要至少兩個方法,分别是完成promise的狀态從pending到resolved或rejected的轉換,同時執行相應的回調隊列,即<code>resolve()</code>和<code>reject()</code>方法。

到此,一個簡單的promise就設計完成了,下面簡單實作下兩個promise化的函數:

由于Promise構造函數接受一個異步任務作為參數,是以<code>getImg</code>還可以這樣調用:

接下來(見證奇迹的時刻),假設有一個BT的需求要這麼實作:異步擷取一個json配置,解析json資料拿到裡邊的圖檔,然後按順序隊列加載圖檔,每張圖檔加載時給個loading效果,

在這裡,<code>Promise.resolve(v)</code>靜态方法隻是簡單傳回一個以v為肯定結果的promise,v可不傳入,也可以是一個函數或者是一個包含<code>then</code>方法的對象或函數(即thenable)。

類似的靜态方法還有<code>Promise.cast(promise)</code>,生成一個以promise為肯定結果的promise;<code>Promise.reject(reason)</code>,生成一個以reason為否定結果的promise。

我們實際的使用場景可能很複雜,往往需要多個異步的任務穿插執行,并行或者串行同在。這時候,可以對Promise進行各種擴充,比如實作<code>Promise.all()</code>,接受promises隊列并等待他們完成再繼續,再比如<code>Promise.any()</code>,promises隊列中有任何一個處于完成态時即觸發下一步操作。

3.标準的Promise

  現今流行的各大js庫,幾乎都不同程度的實作了Promise,如dojo,jQuery、Zepto、when.js、Q等,隻是暴露出來的大都是<code>Deferred</code>對象,當然還有angularJs中的$q.這裡以jQuery為例,說一下:

 二 、用Promise組織你的JavaScript代碼

  上面我們了解了Promise,相信大家對Promise有了一定的認識。下面我們開始動手來寫代碼,通過幾個簡單的例子,來加深了解。這裡我們使用浏覽器自帶的Promise,首先我們要先檢測一些浏覽器是否支援Promise,其實很簡單,如果是谷歌浏覽器,按下F12,打開控制台,如圖:

JavaScript進階之路——認識和使用Promise,重構你的Js代碼

這裡我們可以看到Promise的type是function,也就是說谷歌浏覽器是支援promise的。以此為原理,我們可以寫一段JavaScript代碼來檢測,代碼如下:

經過檢測,發現IE11竟然不支援promise.建議大家用谷歌浏覽器來進行測試吧。

我們首先來寫一個等待的方法,如下:

測試這個方法的代碼如下:wait(5000).then(function(){alert('hello')}),這段代碼很簡單,就是等待5秒以後執行一個回調,彈出一個消息。當然,你還可以這樣寫:

怎麼樣?很簡單吧~~

下面來看一些我從網上收集的一些常用的JavaScript的promise的寫法:

 看到了吧,Promise風格API跟回調風格的API不同,它的參數跟同步的API是一緻的,但是它的傳回值是個Promise對象,要想得到真正的結果,需要在then的回調裡面拿到。

 三、用Promise組織JavaScript異步代碼

   在比較複雜的頁面中,我們會使用到大量的異步操作。我們來看看使用Promise會帶來怎樣的便利吧~~

1、多個異步調用,同步/并行

   例如我們頁面調用了好幾個異步函數,我們要等待所有的異步函數執行完成後,做一些操作,如彈出一個消息框提示使用者操作成功。下面我們拿一個例子來說明一下:

Promise.all跟then的配合,可以視為調用部分參數為Promise提供的函數。譬如,我們現在有一個接受三個參數的函數:

現在我們調用print函數,其中a和b是需要異步擷取的:

如果用callback的話,我們就隻能一個一個調用了,調用完了geta,然後在其回調函數裡面調用getb,最後在getb的回調函數中調用print方法。串行和并行哪個更快,大家很清楚吧~~

 2.競争

   如果說Primise.all是promise對象之間的“與”關系,那麼Promise.race就是promise對象之間的“或”關系。比如,我要實作“點選按鈕或者5秒鐘之後執行”:

3.異常處理

   異常處理一直是回調的難題,而promise提供了非常友善的catch方法:在一次promise調用中,任何的環節發生reject,都可以在最終的catch中捕獲到:

4.複雜流程

接下來,我們來看比較複雜的情況。

promise有一種非常重要的特性:then的參數,理論上應該是一個promise函數,而如果你傳遞的是普通函數,那麼預設會把它當做已經resolve了的promise函數。

這樣的特性讓我們非常容易把promise風格的函數跟已有代碼結合起來。

為了友善傳參數,我們編寫一個currying函數,這是函數式程式設計裡面的基本特性,在這裡跟promise非常搭,是以就實作一下:

currying會給某個函數"固化"幾個參數,并且傳回接受剩餘參數的函數。比如之前的函數,可以這麼玩:

有了currying,我們就可以愉快地來玩鍊式調用了,比如以下代碼:

 四 、總結

   我們看到,不管Promise實作怎麼複雜,但是它的用法卻很簡單,組織的代碼很清晰,從此不用再受callback的折磨了。promise作為一個新的API,它的API本身沒有什麼特别的功能,但是它背後代表的程式設計思路是很有價值的。

最後,Promise是如此的優雅!但Promise也隻是解決了回調的深層嵌套的問題,真正簡化JavaScript異步程式設計的還是Generator,在Node.js端,建議考慮Generator。

 五 、參考資料

JavaScript Promise迷你書(中文版)   http://liubin.github.io/promises-book/

JavaScript Promise啟示錄       http://www.csdn.net/article/2014-05-28/2819979-JavaScript-Promise

用Promise組織程式                 http://www.w3ctech.com/topic/721

QQ交流群:243633526

 部落格位址:http://www.cnblogs.com/yunfeifei/

 聲明:本部落格原創文字隻代表本人工作中在某一時間内總結的觀點或結論,與本人所在機關沒有直接利益關系。非商業,未授權,貼子請以現狀保留,轉載時必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接。

如果大家感覺我的博文對大家有幫助,請推薦支援一把,給我寫作的動力。

繼續閱讀