我們先來看一下編寫AJAX編碼經常遇到的幾個問題:
1.由於AJAX是異步的,所有依賴AJAX返回結果的代碼必需寫在AJAX回調函數中。這就不可避免地形成了嵌套,ajax等異步操作越多,嵌套層次就會越深,代碼可讀性就會越差。
$.ajax({ url: url, data: dataObject, success: function(){ console.log("I depend on ajax result."); }, error: function(){} }); console.log("I will print before ajax finished.");
2.如果AJAX請求之間存在依賴關系,我們的代碼就會形成Pyramid of Doom(金字塔厄運)。比如我們要完成這樣一件事:有4個供Ajax訪問的url地址,需要先Ajax訪問第1個,在第1個訪問完成后,用拿到的返回數據作為參數再訪問第2個,第2個訪問完成后再第3個...以此到4個全部訪問完成。按照這樣的寫法,似乎會變成這樣:
$.ajax({ url: url1, success: function(data){ $.ajax({ url: url2, data: data, success: function(data){ $.ajax({ //... }); } }); } });
3.考慮這種場景,假如我們同時發送兩個Ajax請求,然后要在兩個請求都成功返回后再做一件接下來的事,想一想如果只按前面的方式在各自的調用位置去附加回調,這是不是很困難?
可以看到:JavaScript中類似於AJAX這種異步的操作,會導致代碼嵌套層次復雜,可讀性差,有的時候甚至是實現需求都非常困難。為了解決這種異步回調難的問題,CommonJS組織制定了異步模式編程規范Promises/A。目前該規范已經有了很多的實現者,比如Q, when.js, jQuery.Deffered()等。我們以jQuery.Deffered學習下Promise。
Promise的狀態
Promise對象有3種可能的狀態:肯定狀態(resolved)、否定狀態(rejected)、等待狀態(pending)。剛開始創建的Promise對象處於pending狀態,只能從pending變成resolved或者是從pending變成rejected狀態。
var df1 = $.Deferred(); console.log(df1.state());//pending var df2 = $.Deferred(); df2.resolve();//resolved console.log(df2.state()); var df3 = $.Deferred(); df3.reject(); console.log(df3.state());//rejected
$.Deferred()創建一個延遲對象(也就是Promise對象),deferred.state()可以獲取Promise對象當前所處的狀態。deferred.resolve()和deferred.reject()則是用來改變Promise對象的狀態。
Promise添加回調函數
Promise對象有3種狀態,我們可以分別為這3種狀態注冊回調函數。當Promise處於某個狀態的時候,會觸發這個狀態下注冊的回調函數。
var df = $.Deferred(); df.done(function(){alert("success");}); df.fail(function(){alert("fail");}); df.progress(function(){alert("progress");}); df.notify(); df.resolve(); // df.reject();
done()、fail()、progress()分別注冊resolved、rejected、pending狀態下的回調函數。通過resolve()、reject()、notify()可以觸發事先注冊的回調函數。
Promise是支持鏈式調用的,上面的代碼可以寫成下面的樣子。
var df = $.Deferred(); df.done(function(){alert("success");}) .fail(function(){alert("fail");}) .progress(function(){alert("progress");});
Promise支持多個回調函數,會按照注冊順序調用。
var df = $.Deferred(); df.done(function(){alert("first");}) .fail(function(){alert("fail");}); df.done(function(){alert("second");}); df.done(function(){alert("third");}); df.resolve();
deferred.always()添加的回調函數,無論Promise是resolved狀態還是rejected狀態,都會被調用。
var df1 = $.Deferred(); df1.always(function(type){alert(type);}); df1.resolve("resolve"); var df2 = $.Deferred(); df2.always(function(type){alert(type);}); df2.reject("reject");
progress()和notify()能夠用來實現進度條效果,因為notify()允許調用多次,而reject()和resolve()只能調用一次。這個很好理解,因為一旦狀態變成resolved或者是rejected,就不能再改變其狀態,也沒有必要。
var df = $.Deferred(); df.done(function(){alert("success");}); df.fail(function(){alert("fail");}); df.progress(function(){alert("progress");}); // resolve()調用2次,但是只能觸發1次success df.resolve(); df.resolve(); var mudf = $.Deferred(); mudf.done(function(){alert("success");}); mudf.fail(function(){alert("fail");}); mudf.progress(function(){alert("progress");}); // 每次調用notify都會觸發progress回調函數 mudf.notify("%10"); mudf.notify("%20");
rejectWith()、resolveWith()、notifyWith()功能上和reject()、resolve()、notify()沒有什么差別,主要差別在於回調函數中的執行上下文(方法中的this)和參數形式。具體差別可以參考"JQuery.Callbacks系列一:api使用
// 老的ajax寫法 $.ajax({ url: "test.html", success: function(){ alert("success"); }, error:function(){ alert("error"); } }); // 使用promise后的寫法 $.ajax("test.html") .done(function(){}) .fail(function(){}) .done(function(){) .fail(function(){);
JQuery中的Deferred對象與Promise對象區別
JQuery.Deferred相關的API,有的返回的是Deferred對象,有的返回的是Promise對象。如done()、reject()等大部分函數返回的都是Deferred對象,$.when()和then()函數返回的是Promise對象。具體可以參考JQuery API文檔。
JQuery官方對Promise Objects的解釋是:
This object provides a subset of the methods of the Deferred object (then, done, fail, always, progress, state and promise) to prevent users from changing the state of the Deferred.
可以看到Promise對象其實就是Deferred對象的一部分,Deferred對象提供了notify、reject、resolve等改變狀態的方法,但是Promise對象沒有提供這些方法。
文章開始提到的AJAX問題1~3,問題1可以很容易通過Promise得到解決。問題2和問題3是通過$.when()和deferred.then()得到解決,由於這2個API相對來說復雜一些,以后的文章再分析這2個API。
詳解"這篇文章中的fire()和fireWith()。
上面簡單的介紹了Promise的使用方式,我們可以用Promise的方式來編寫AJAX代碼。可以很容易地看出:使用Promise后代碼嵌套層次少了,代碼是縱向增長的,而不再是橫向增長。而且使用Promise,可以指定多個ajax回調函數。
function ok(name){ var dfd = new $.Deferred(); callback:func(){ return dfd.resolve( response ); } return dfd.promise(); } $.when(ok(1),ok(2)).then(function(resp1,resp2){})
//相關API 分成3類
1類:$.when(pro1,pro1) 將多個 promise 對象以and的關系 合並為1個
2類:promise 激發為 解決 deferred.resolve([ args ] ) deferred.resolveWith( context, [ args ] )
和 拒絕 .reject .rejectWith
context 上下文 替換 this 和通知 .notify .notifyWith
3類: 對激發的響應 解決時deferred.done(args) 拒絕時 deferred.fail() 通知時 deferred.progress()
不管 解決 或 拒絕 deferred.always()
deferred.then( doneCallbacks, failCallbacks [, progressCallbacks] )
promise(或者叫deferred 延遲對象如何獲取?)
var dfd = new $.Deferred(); return dfd.promise();
返回promise當前狀態
deferred.state() pending(尚未完成) resolved rejected
管道
deferred.pipe( [ doneFilter ], [ failFilter ] ) var defer = $.Deferred() var filtered = defer.pipe( null, function( value ) { return value * 3; }); defer.reject( 6 ); filtered.fail(function( value ) { alert( "Value is ( 3*6 = ) 18: " + value ); });