Promise對象和Deferred對象

一、從Ajax請求說起

Promise和Deferred對象爲我們提供了一種很優雅的異步處理方案,Promise最開始出現於Dojo框架中,09年Kris Zyp有感於dojo.Deferred提出了CommonJS之Promise/A規範,jQuery從1.5版本開始實現了這一重量級方案,不過沒有嚴格按照規範進行實現,有一些API上的差異。說到這裏,先來看下這兩個對象能夠做些什麼吧!

從一個ajax請求說起,如果有一個異步的ajax請求,按照一般的實現方式,包括我現在也還在這樣做:

JavaScript代碼
  1.     
  2. $.get('/myRequest',function(){      
  3.    success: onSuccess,      
  4.    failure: onFailure,      
  5.    always : onAlways      
  6. })  

在這種常規的實現方案中,會把異步的回調參數和請求參數寫在一起做爲ajax的參數,但是如果通過promise的實現方式,這種寫法可以轉變爲這樣:

JavaScript代碼
  1.     
  2. var promise = $.get("/myRequest");      
  3. promise.done(onSuccess);      
  4. promise.fail(onFailure);      
  5. promise.always(onAlways);  

這種方式和普通的調用方式有什麼不同的地方呢?

在這種方式中可以和EventEmitter一樣可以任意添加done、fail、always的處理方法,最主要的作用還是把請求與回調進行了分離與解耦,通過這樣的封裝還可以做更多好玩的事,接着往下看吧。

promise對象中的方法只能被動的被執行,如果需要主動觸發這些事件怎麼辦呢,這時我們可以使用Deferred對象,jQuery中可以這樣使用(摘自《javascript異步編程》):

JavaScript代碼
  1.     
  2. var prompt = new $.Deferered();      
  3. prompt.always(function(){      
  4.    console.info("A choice was made:);    
  5. });     
  6. prompt.done(function(){     
  7.    console.info("Starting game...");    
  8. });     
  9. prompt.fail(function(){     
  10.    console.info("No game tody.");    
  11. });     
  12. $("#playGame").focus.on("keypress",function(e){    
  13.    if(e.keyCode === "121"){      
  14.       prompt.resolve();      
  15.    }else if(e.keyCode == 110){      
  16.       prompt.reject();      
  17.    }else{      
  18.       return false;      
  19.    }      
  20. })  

這樣就可以通過鍵盤發出不同的指令了,Promise與Deferred對象不同的地方便是Deferred可以通過resolve和reject方法觸發對應的回調,Deferred是Promise對象的一個超集,可以通過Deferred對象的promise方法獲取對應的Promise對象,說到這裏,有一個很關鍵的名詞 -state(狀態),在對象內部會維持這樣一個狀態值的變化,這樣的狀態控制機制使得done、fail 方法只會被響應一次:

二、jQuery中使用promise和deferred對象

promise和deferred對象被發揚光大一定程度上是由於在jQuery中得到支持,這裏簡單介紹一些jQuery中的主要用法:

1、promise對象的鏈式調用

JavaScript代碼
  1.     
  2. $.ajax("test.cgi").      
  3.   done(function(){console.info("called first time");}).      
  4.   fail(function(){console.info("error");}).      
  5.   done(function(){console.info("called second time")});  

2、處理動畫回調

JavaScript代碼
  1.     
  2. var $flash = $("#flash");      
  3. var showpromise = $flash.fadeIn().promise();      
  4. var hidePromise = $flash.fadeOut().promise();      
  5. showpromise.done(function(){console.info("fadeIn call back")});      
  6. hidePromise.done(function(){console.info("fadeOut call back")})  

通過promise對象把動畫與回調進行了很好的解耦,無需再把callback以參數的形式進行傳遞,這只是jQuery在1.6以及1.7中的權宜之計,使用Deferred對象也可以達到同樣的效果

JavaScript代碼
  1.     
  2. var $flash = $("#flash");      
  3. var defer = new $.Deferred();      
  4. $flash.fadeOut(defer.resolve);      
  5. defer.done(function(){console.info("fadeIn call back")});  

3、如何給回調傳遞參數

JavaScript代碼
  1.     
  2. var def = new $.Deferred();      
  3. def.done(function(name){      
  4.    console.info("done with name %s",name);      
  5. });      
  6. def.resolve("test");      
  7. //也可以傳入上下文對象      
  8. def.resolveWith(context,"test");      
  9. //也可以這樣調用      
  10. def.resolve.call(context,"test");  

4、進度通知

Promise主要提供對結果的監控,在jQuery中也可以通過progress事件對過程進行處理

JavaScript代碼
  1.     
  2. var def = new $.Deferred();      
  3. def.progress(function(count){      
  4.    console.info(count);      
  5. });      
  6. def.notify(1);      
  7. def.notify(2);      
  8. ...  

5、對多個異步事件指定回調

JavaScript代碼
  1.     
  2. $.when($.post("/1",data1),$.post("/2",data2)).      
  3.   then(onSuccess,onFailure);  

當兩個post請求結束時纔會執行then方法,then方法的第一個參數做爲done的回調,第二個參數作爲failure的回調,如果只有一個參數則作爲done的回調方法。那如何處理每一個Promise對象的參數呢?每一個Promise對象返回的結果會按照順序傳給done方法,所以我們可以這樣使用:

JavaScript代碼
  1.     
  2. $.when($.post("/1",data1),$.post("/2",data2)).      
  3.   done(function(post1_Args,post2_Args){      
  4.      //...      
  5.   });  

但是這樣寫法不是很被推薦,可讀性較好的寫法可以這樣處理多個promise對象的結果:

JavaScript代碼
  1.     
  2. var data = {};      
  3. var getting1 = $.get("/1").      
  4.     done(function(result){data["1"] = result;});      
  5. var getting2 = $.get("/2").      
  6.     done(function(result){data["2"]= result;});      
  7. $.when(getting1,getting2).done(function(){      
  8.     //此處訪問data已經拿到兩個promise對象返回的數據      
  9. });  

如果$.when傳入的參數不是Promise或者Deferred對象,則會視爲一個已經resolved的Deferred對象並立即執行,如下所示:

JavaScript代碼
  1.     
  2. $.when({test:"123"}).done(function(data){      
  3.    console.info(data.test); // ==> 123      
  4. })      
  5. //或者這樣寫      
  6. $.when(test()).done(function(data){      
  7.    console.info(data.test); // ==> 123      
  8. })      
  9. function test(){      
  10.    return {test: "123"}      
  11. }  

基於when方法這樣的特性,如果when方法的參數是function並且返回的Deferred對象,則可以做這樣一些異步的處理:

JavaScript代碼
  1.     
  2. var wait1 = function(){      
  3.    var def = $.Deferred();      
  4.    var tasks = function(){        
  5.       def.resolve("方法1執行成功");      
  6.    };      
  7.    setTimeout(tasks,1000);      
  8.    return def;      
  9. }      
  10. var wait2 = function(){      
  11.    var def = $.Deferred();      
  12.    var tasks = function(){        
  13.       def.resolve("方法2執行成功");      
  14.    };      
  15.    setTimeout(tasks,2000);      
  16.    return def;      
  17. }      
  18. $.when(wait1(),wait2()).done(function(data1,data2){      
  19.    alert(data1 + "," + data2) // ==> 方法1執行成功,方法2執行成功      
  20. });  

寫到這裏,我不得不吐槽一下《javascript異步編程》這本書的翻譯者,將這一部分翻譯的非常僵硬,一個很簡單的原理用一些很難理解的詞彙來解釋。

6、管道接口

從jQuery 1.8開始的版本中開始廢棄掉 pipe 接口,而改爲用then方法替代它,then接口支持三個參數:deferred.then( doneFilter [, failFilter ] [, progressFilter ] ) ,這樣可以支持複雜的鏈式調用,示例如下:

JavaScript代碼
  1.     
  2. var request = $.ajax(url, { dataType:"json" } ),      
  3.     chained = request.then(function( data ) {      
  4.         return $.ajax( url2, {data:{user: data.userId}});      
  5.     });      
  6. chained.done(function( data ) {      
  7.      // data retrieved from url2 as provided by the first request      
  8. });  

這太簡單了,那能不能利用then方法做一些更復雜的邏輯呢?當然可以,示例如下:

JavaScript代碼
  1.     
  2. var username = "test";              
  3. var password = "123456";                    
  4. $.getJSON("user.json").then(function (data) {                      
  5.     if(checkUser(data, username, password)) {                        
  6.         return $.getJSON("status.json");    //校驗通過,聲明一個新的Promise對象到then方法中進行二次校驗                 
  7.     }else{                                
  8.         var def = $.Deferred();                  
  9.         def.reject("用戶名或者密碼不正確");  //用戶名校驗錯誤,拋出Deferred對象,執行最後的fail方法                              
  10.         return def;                                    
  11.     }      
  12. },function(data){   //如果user.json請求發生異常,執行這裏,並將錯誤處理拋給最後綁定的fail方法,這裏也可以不做處理,直接響應最後的fail方法      
  13.     var def = $.Deferred();      
  14.     def.reject("請求user.json失敗");      
  15.     return def;      
  16. }).then(function (data) {        
  17.     if(checkStatus(data, username)) {        
  18.         return username;//返回非Promise或者Deferred對象,則當做已經resolved的Deferred對象並將該值作爲參數執行done方法              
  19.     } else {                    
  20.         var def = $.Deferred();              
  21.         def.reject("用戶已經被停用!請與管理員聯繫!");                  
  22.         return def;                    
  23.     }}).done(function (data) {         
  24.         alert("歡迎你" + data +"登陸我們的系統!");      
  25.     }).fail(function (data) {                
  26.         alert(data);      
  27.     });       
  28.                
  29. function checkUser(){      
  30.   return false;      
  31. }      
  32. function checkStatus(){      
  33.   return true;      
  34. }  

關於這Promise和Deferred對象的使用方法基本如此,總的來說Promise和Deferred對象給我們提供了一種比較新穎的異步編程解耦方案,如果條件允許可以實踐中多多應用~~


轉載自:http://www.chauvetxiao.com/bo-blog/read.php?33

發佈了17 篇原創文章 · 獲贊 8 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章