一、從Ajax請求說起
Promise和Deferred對象爲我們提供了一種很優雅的異步處理方案,Promise最開始出現於Dojo框架中,09年Kris Zyp有感於dojo.Deferred提出了CommonJS之Promise/A規範,jQuery從1.5版本開始實現了這一重量級方案,不過沒有嚴格按照規範進行實現,有一些API上的差異。說到這裏,先來看下這兩個對象能夠做些什麼吧!
從一個ajax請求說起,如果有一個異步的ajax請求,按照一般的實現方式,包括我現在也還在這樣做:
- $.get('/myRequest',function(){
- success: onSuccess,
- failure: onFailure,
- always : onAlways
- })
在這種常規的實現方案中,會把異步的回調參數和請求參數寫在一起做爲ajax的參數,但是如果通過promise的實現方式,這種寫法可以轉變爲這樣:
- var promise = $.get("/myRequest");
- promise.done(onSuccess);
- promise.fail(onFailure);
- promise.always(onAlways);
這種方式和普通的調用方式有什麼不同的地方呢?
在這種方式中可以和EventEmitter一樣可以任意添加done、fail、always的處理方法,最主要的作用還是把請求與回調進行了分離與解耦,通過這樣的封裝還可以做更多好玩的事,接着往下看吧。
promise對象中的方法只能被動的被執行,如果需要主動觸發這些事件怎麼辦呢,這時我們可以使用Deferred對象,jQuery中可以這樣使用(摘自《javascript異步編程》):
- var prompt = new $.Deferered();
- prompt.always(function(){
- console.info("A choice was made:);
- });
- prompt.done(function(){
- console.info("Starting game...");
- });
- prompt.fail(function(){
- console.info("No game tody.");
- });
- $("#playGame").focus.on("keypress",function(e){
- if(e.keyCode === "121"){
- prompt.resolve();
- }else if(e.keyCode == 110){
- prompt.reject();
- }else{
- return false;
- }
- })
這樣就可以通過鍵盤發出不同的指令了,Promise與Deferred對象不同的地方便是Deferred可以通過resolve和reject方法觸發對應的回調,Deferred是Promise對象的一個超集,可以通過Deferred對象的promise方法獲取對應的Promise對象,說到這裏,有一個很關鍵的名詞 -state(狀態),在對象內部會維持這樣一個狀態值的變化,這樣的狀態控制機制使得done、fail 方法只會被響應一次:
二、jQuery中使用promise和deferred對象
promise和deferred對象被發揚光大一定程度上是由於在jQuery中得到支持,這裏簡單介紹一些jQuery中的主要用法:
1、promise對象的鏈式調用
- $.ajax("test.cgi").
- done(function(){console.info("called first time");}).
- fail(function(){console.info("error");}).
- done(function(){console.info("called second time")});
2、處理動畫回調
- var $flash = $("#flash");
- var showpromise = $flash.fadeIn().promise();
- var hidePromise = $flash.fadeOut().promise();
- showpromise.done(function(){console.info("fadeIn call back")});
- hidePromise.done(function(){console.info("fadeOut call back")})
通過promise對象把動畫與回調進行了很好的解耦,無需再把callback以參數的形式進行傳遞,這只是jQuery在1.6以及1.7中的權宜之計,使用Deferred對象也可以達到同樣的效果
- var $flash = $("#flash");
- var defer = new $.Deferred();
- $flash.fadeOut(defer.resolve);
- defer.done(function(){console.info("fadeIn call back")});
3、如何給回調傳遞參數
- var def = new $.Deferred();
- def.done(function(name){
- console.info("done with name %s",name);
- });
- def.resolve("test");
- //也可以傳入上下文對象
- def.resolveWith(context,"test");
- //也可以這樣調用
- def.resolve.call(context,"test");
4、進度通知
Promise主要提供對結果的監控,在jQuery中也可以通過progress事件對過程進行處理
- var def = new $.Deferred();
- def.progress(function(count){
- console.info(count);
- });
- def.notify(1);
- def.notify(2);
- ...
5、對多個異步事件指定回調
- $.when($.post("/1",data1),$.post("/2",data2)).
- then(onSuccess,onFailure);
當兩個post請求結束時纔會執行then方法,then方法的第一個參數做爲done的回調,第二個參數作爲failure的回調,如果只有一個參數則作爲done的回調方法。那如何處理每一個Promise對象的參數呢?每一個Promise對象返回的結果會按照順序傳給done方法,所以我們可以這樣使用:
- $.when($.post("/1",data1),$.post("/2",data2)).
- done(function(post1_Args,post2_Args){
- //...
- });
但是這樣寫法不是很被推薦,可讀性較好的寫法可以這樣處理多個promise對象的結果:
- var data = {};
- var getting1 = $.get("/1").
- done(function(result){data["1"] = result;});
- var getting2 = $.get("/2").
- done(function(result){data["2"]= result;});
- $.when(getting1,getting2).done(function(){
- //此處訪問data已經拿到兩個promise對象返回的數據
- });
如果$.when傳入的參數不是Promise或者Deferred對象,則會視爲一個已經resolved的Deferred對象並立即執行,如下所示:
- $.when({test:"123"}).done(function(data){
- console.info(data.test); // ==> 123
- })
- //或者這樣寫
- $.when(test()).done(function(data){
- console.info(data.test); // ==> 123
- })
- function test(){
- return {test: "123"}
- }
基於when方法這樣的特性,如果when方法的參數是function並且返回的Deferred對象,則可以做這樣一些異步的處理:
- var wait1 = function(){
- var def = $.Deferred();
- var tasks = function(){
- def.resolve("方法1執行成功");
- };
- setTimeout(tasks,1000);
- return def;
- }
- var wait2 = function(){
- var def = $.Deferred();
- var tasks = function(){
- def.resolve("方法2執行成功");
- };
- setTimeout(tasks,2000);
- return def;
- }
- $.when(wait1(),wait2()).done(function(data1,data2){
- alert(data1 + "," + data2) // ==> 方法1執行成功,方法2執行成功
- });
寫到這裏,我不得不吐槽一下《javascript異步編程》這本書的翻譯者,將這一部分翻譯的非常僵硬,一個很簡單的原理用一些很難理解的詞彙來解釋。
6、管道接口
從jQuery 1.8開始的版本中開始廢棄掉 pipe 接口,而改爲用then方法替代它,then接口支持三個參數:deferred.then( doneFilter [, failFilter ] [, progressFilter ] ) ,這樣可以支持複雜的鏈式調用,示例如下:
- var request = $.ajax(url, { dataType:"json" } ),
- chained = request.then(function( data ) {
- return $.ajax( url2, {data:{user: data.userId}});
- });
- chained.done(function( data ) {
- // data retrieved from url2 as provided by the first request
- });
這太簡單了,那能不能利用then方法做一些更復雜的邏輯呢?當然可以,示例如下:
- var username = "test";
- var password = "123456";
- $.getJSON("user.json").then(function (data) {
- if(checkUser(data, username, password)) {
- return $.getJSON("status.json"); //校驗通過,聲明一個新的Promise對象到then方法中進行二次校驗
- }else{
- var def = $.Deferred();
- def.reject("用戶名或者密碼不正確"); //用戶名校驗錯誤,拋出Deferred對象,執行最後的fail方法
- return def;
- }
- },function(data){ //如果user.json請求發生異常,執行這裏,並將錯誤處理拋給最後綁定的fail方法,這裏也可以不做處理,直接響應最後的fail方法
- var def = $.Deferred();
- def.reject("請求user.json失敗");
- return def;
- }).then(function (data) {
- if(checkStatus(data, username)) {
- return username;//返回非Promise或者Deferred對象,則當做已經resolved的Deferred對象並將該值作爲參數執行done方法
- } else {
- var def = $.Deferred();
- def.reject("用戶已經被停用!請與管理員聯繫!");
- return def;
- }}).done(function (data) {
- alert("歡迎你" + data +"登陸我們的系統!");
- }).fail(function (data) {
- alert(data);
- });
- function checkUser(){
- return false;
- }
- function checkStatus(){
- return true;
- }
關於這Promise和Deferred對象的使用方法基本如此,總的來說Promise和Deferred對象給我們提供了一種比較新穎的異步編程解耦方案,如果條件允許可以實踐中多多應用~~
轉載自:http://www.chauvetxiao.com/bo-blog/read.php?33