JavaScript Promise啓示錄--(轉)

本博文轉至:http://www.csdn.net/article/2014-05-28/2819979-JavaScript-Promisehtml

 

【編者按】JavaScript是一種基於對象和事件驅動並具備相對安全性的客戶端腳本語言。自推出後就大受開發者的青睞,基於JavaScript的開發工具也不可勝數,開發者們能夠靈活選擇,輕鬆構建應用。原文做者TAT.dmyang就JavaScript中的Promise規範給出了一些看法,目前高級瀏覽器如Chrome、Firefox都已經內置了Promise對象,提供更多的操做接口,如此優雅的Promise具有哪些特性呢?且看下文:前端


一直以來,JavaScript處理異步都是以callback的方式,在前端開發領域callback機制幾乎深刻人心。在設計API的時候,不論是瀏覽器廠商仍是SDK開發商亦或是各類類庫的做者,基本上都已經遵循着callback的套路。html5

近幾年隨着JavaScript開發模式的逐漸成熟,CommonJS規範順勢而生,其中就包括提出了Promise規範,Promise徹底改變了js異步編程的寫法,讓異步編程變得十分的易於理解。node

在callback的模型裏邊,咱們假設須要執行一個異步隊列,代碼看起來可能像這樣:jquery

1 loadImg('a.jpg', function() {  
2     loadImg('b.jpg', function() {  
3         loadImg('c.jpg', function() {  
4             console.log('all done!');  
5         });  
6     });  
7 });  

 

這也就是咱們常說的回調金字塔,當異步的任務不少的時候,維護大量的callback將是一場災難。當今Node.js大熱,好像不少團隊都要用它來作點東西以沾沾「洋氣」,曾經跟一個運維的同窗聊天,他們也是打算使用Node.js作一些事情,但是一想到js的層層回調就望而卻步。git

好,扯淡完畢,下面進入正題。es6

Promise可能你們都不陌生,由於Promise規範已經出來好一段時間了,同時Promise也已經歸入了ES6,並且高版本的chrome、firefox瀏覽器都已經原生實現了Promise,只不過和現現在流行的類Promise類庫相比少些API。github

所謂Promise,字面上能夠理解爲「承諾」,就是說A調用B,B返回一個「承諾」給A,而後A就能夠在寫計劃的時候這麼寫:當B返回結果給個人時候,A執行方案S1,反之若是B由於什麼緣由沒有給到A想要的結果,那麼A執行應急方案S2,這樣一來,全部的潛在風險都在A的可控範圍以內了。ajax

上面這句話,翻譯成代碼相似:chrome

1 var resB = B();  
2 var runA = function() {  
3     resB.then(execS1, execS2);  
4 };  
5 runA();  

 

只看上面這行代碼,好像看不出什麼特別之處。但現實狀況可能比這個複雜許多,A要完成一件事,可能要依賴不止B一我的的響應,可能須要同時向多我的詢問,當收到全部的應答以後再執行下一步的方案。最終翻譯成代碼可能像這樣:

 1 var resB = B();  
 2 var resC = C();  
 3 ...  
 4   
 5 var runA = function() {  
 6     reqB  
 7         .then(resC, execS2)  
 8         .then(resD, execS3)  
 9         .then(resE, execS4)  
10         ...  
11         .then(execS1);  
12 };  
13   
14 runA();

 

在這裏,當每個被詢問者作出不符合預期的應答時都用了不一樣的處理機制。事實上,Promise規範沒有要求這樣作,你甚至能夠不作任何的處理(即不傳入then的第二個參數)或者統一處理。

好了,下面咱們來認識下Promise/A+規範

  • 一個promise可能有三種狀態:等待(pending)、已完成(fulfilled)、已拒絕(rejected)
  • 一個promise的狀態只可能從「等待」轉到「完成」態或者「拒絕」態,不能逆向轉換,同時「完成」態和「拒絕」態不能相互轉換
  • promise必須實現then方法(能夠說,then就是promise的核心),並且then必須返回一個promise,同一個promise的then能夠調用屢次,而且回調的執行順序跟它們被定義時的順序一致
  • then方法接受兩個參數,第一個參數是成功時的回調,在promise由「等待」態轉換到「完成」態時調用,另外一個是失敗時的回調,在promise由「等待」態轉換到「拒絕」態時調用。同時,then能夠接受另外一個promise傳入,也接受一個「類then」的對象或方法,即thenable對象。

能夠看到,Promise規範的內容並不算多,你們能夠試着本身實現如下Promise。

如下是筆者本身在參考許多類Promise庫以後簡單實現的一個Promise,代碼請移步promiseA

簡單分析下思路:

構造函數Promise接受一個函數resolver,能夠理解爲傳入一個異步任務,resolver接受兩個參數,一個是成功時的回調,一個是失敗時的回調,這兩參數和經過then傳入的參數是對等的。

其次是then的實現,因爲Promise要求then必須返回一個promise,因此在then調用的時候會新生成一個promise,掛在當前promise的_next上,同一個promise屢次調用都只會返回以前生成的_next

因爲then方法接受的兩個參數都是可選的,並且類型也沒限制,能夠是函數,也能夠是一個具體的值,還能夠是另外一個promise。下面是then的具體實現:

 1 Promise.prototype.then = function(resolve, reject) {  
 2     var next = this._next || (this._next = Promise());  
 3     var status = this.status;  
 4     var x;  
 5   
 6     if('pending' === status) {  
 7         isFn(resolve) && this._resolves.push(resolve);  
 8         isFn(reject) && this._rejects.push(reject);  
 9         return next;  
10     }  
11   
12     if('resolved' === status) {  
13         if(!isFn(resolve)) {  
14             next.resolve(resolve);  
15         } else {  
16             try {  
17                 x = resolve(this.value);  
18                 resolveX(next, x);  
19             } catch(e) {  
20                 this.reject(e);  
21             }  
22         }  
23         return next;  
24     }  
25   
26     if('rejected' === status) {  
27         if(!isFn(reject)) {  
28             next.reject(reject);  
29         } else {  
30             try {  
31                 x = reject(this.reason);  
32                 resolveX(next, x);  
33             } catch(e) {  
34                 this.reject(e);  
35             }  
36         }  
37         return next;  
38     }  
39 };  

 

這裏,then作了簡化,其餘promise類庫的實現比這個要複雜得多,同時功能也更多,好比還有第三個參數——notify,表示promise當前的進度,這在設計文件上傳等時頗有用。對then的各類參數的處理是最複雜的部分,有興趣的同窗能夠參看其餘類Promise庫的實現。

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

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

 1 function sleep(ms) {  
 2     return function(v) {  
 3         var p = Promise();  
 4   
 5         setTimeout(function() {  
 6             p.resolve(v);  
 7         });  
 8   
 9         return p;  
10     };  
11 };  
12   
13 function getImg(url) {  
14     var p = Promise();  
15     var img = new Image();  
16   
17     img.onload = function() {  
18         p.resolve(this);  
19     };  
20   
21     img.onerror = function(err) {  
22         p.reject(err);  
23     };  
24   
25     img.url = url;  
26   
27     return p;  
28 };  

 

因爲Promise構造函數接受一個異步任務做爲參數,因此getImg還能夠這樣調用:

 1 function getImg(url) {  
 2     return Promise(function(resolve, reject) {  
 3         var img = new Image();  
 4   
 5         img.onload = function() {  
 6             resolve(this);  
 7         };  
 8   
 9         img.onerror = function(err) {  
10             reject(err);  
11         };  
12   
13         img.url = url;  
14     });  
15 };  

 

接下來(見證奇蹟的時刻),假設有一個BT的需求要這麼實現:異步獲取一個json配置,解析json數據拿到裏邊的圖片,而後按順序隊列加載圖片,沒張圖片加載時給個loading效果

 1 function addImg(img) {  
 2     $('#list').find('> li:last-child').html('').append(img);  
 3 };  
 4   
 5 function prepend() {  
 6     $('<li>')  
 7         .html('loading...')  
 8         .appendTo($('#list'));  
 9 };  
10   
11 function run() {  
12     $('#done').hide();  
13     getData('map.json')  
14         .then(function(data) {  
15             $('h4').html(data.name);  
16   
17             return data.list.reduce(function(promise, item) {  
18                 return promise  
19                     .then(prepend)  
20                     .then(sleep(1000))  
21                     .then(function() {  
22                         return getImg(item.url);  
23                     })  
24                     .then(addImg);  
25             }, Promise.resolve());  
26         })  
27         .then(sleep(300))  
28         .then(function() {  
29             $('#done').show();  
30         });  
31 };  
32   
33 $('#run').on('click', run);  

 

這裏的sleep只是爲了看效果加的,可猛擊查看demo!固然,Node.js的例子可查看這裏

在這裏,Promise.resolve(v)靜態方法只是簡單返回一個以v爲確定結果的promise,v可不傳入,也能夠是一個函數或者是一個包含then方法的對象或函數(即thenable)。

相似的靜態方法還有Promise.cast(promise),生成一個以promise爲確定結果的promise;

Promise.reject(reason),生成一個以reason爲否認結果的promise。

咱們實際的使用場景可能很複雜,每每須要多個異步的任務穿插執行,並行或者串行同在。這時候,能夠對Promise進行各類擴展,好比實現Promise.all(),接受promises隊列並等待他們完成再繼續,再好比Promise.any(),promises隊列中有任何一個處於完成態時即觸發下一步操做。

標準的Promise

可參考html5rocks的這篇文章JavaScript Promises,目前高級瀏覽器如Chrome、Firefox都已經內置了Promise對象,提供更多的操做接口,好比Promise.all(),支持傳入一個promises數組,當全部promises都完成時執行then,還有就是更加友好強大的異常捕獲,應對平常的異步編程,應該足夠了。

第三方庫的Promise

現今流行的各大js庫,幾乎都不一樣程度的實現了Promise,如dojo,jQuery、Zepto、when.js、Q等,只是暴露出來的大都是Deferred對象,以jQuery(Zepto相似)爲例,實現上面的getImg()

 1 function getImg(url) {  
 2     var def = $.Deferred();  
 3     var img = new Image();  
 4   
 5     img.onload = function() {  
 6         def.resolve(this);  
 7     };  
 8   
 9     img.onerror = function(err) {  
10         def.reject(err);  
11     };  
12   
13     img.src = url;  
14   
15     return def.promise();  
16 };  

 

固然,jQuery中,不少的操做都返回的是Deferred或promise,如animateajax

// animate  
$('.box')  
    .animate({'opacity': 0}, 1000)  
    .promise()  
    .then(function() {  
        console.log('done');  
    });  
  
// ajax  
$.ajax(options).then(success, fail);  
$.ajax(options).done(success).fail(fail);  
  
// ajax queue  
$.when($.ajax(options1), $.ajax(options2))  
    .then(function() {  
        console.log('all done.');  
    }, function() {  
        console.error('There something wrong.');  
    });  

 

jQuery還實現了done()fail()方法,其實都是then方法的shortcut。

處理promises隊列,jQuery實現的是$.when()方法,用法和Promise.all()相似。

其餘類庫,這裏值得一提的是when.js,自己代碼很少,完整實現Promise,同時支持browser和Node.js,並且提供更加豐富的API,是個不錯的選擇。這裏限於篇幅,再也不展開。

尾聲

咱們看到,無論Promise實現怎麼複雜,可是它的用法卻很簡單,組織的代碼很清晰,今後不用再受callback的折磨了。

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

 

參考文獻

 

本博文轉至:http://www.csdn.net/article/2014-05-28/2819979-JavaScript-Promise

相關文章
相關標籤/搜索