標籤:jquery.deferred使用方式 為什麼要使用jquery.deferre 改善javascript非同步代碼 回調地獄
我們先來看一下編寫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());//pendingvar df2 = $.Deferred(); df2.resolve();//resolvedconsole.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次successdf.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使用詳解"這篇文章中的fire()和fireWith()。
上面簡單的介紹了Promise的使用方式,我們可以用Promise的方式來編寫AJAX代碼。可以很容易地看出:使用Promise後代碼嵌套層次少了,代碼是縱向增長的,而不再是橫向增長。而且使用Promise,可以指定多個ajax回呼函數。
// 老的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。
參考文章
"非同步JavaScript與Promise"作者ACGTOFE
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
javascript非同步代碼的回調地獄以及JQuery.deferred提供的promise解決方案