開發網站的過程當中,咱們常常遇到某些耗時很長的javascript操做。其中,既有異步的操做(好比ajax讀取服務器數據),也有同步的操做(好比遍歷一個大型數組),它們都不是當即能獲得結果的。javascript
一般的作法是,爲它們指定回調函數(callback)。即事先規定,一旦它們運行結束,應該調用哪些函數。html
簡單說,deferred對象就是jQuery的回調函數解決方案。在英語中,defer的意思是"延遲",因此deferred對象的含義就是"延遲"到將來某個點再執行。java
它解決了如何處理耗時操做的問題,對那些操做提供了更好的控制,以及統一的編程接口。它的主要功能,能夠歸結爲四點。jquery
咱們來看一個$.ajax請求的例子:ajax
$.ajax({ url: 'test.jspx', success: function(data) { // 請求成功了 // TODO }, error: function(error) { // 請求失敗了 // TODO } });
上面的代碼再常見不過了,不過下面的寫法你也必定看過:編程
$.ajax('test.jspx') .done(function(data) { // 請求成功了 // TODO }) .fail(function(error) { // 請求失敗了 // TODO }); $.ajax({ url: 'test.jspx', type:'POST', data: data }).done(function(data) { // 請求成功了 // TODO }).fail(function(error) { // 請求失敗了 // TODO });
其實在1.5.0版本的以前的jquery是不支持鏈式寫法的,只能使用第一種寫法,緣由是此版本以前的jquery的ajax操做返回的是一個XHR (XMLHttpRequest)
對象,這個對象沒有像done
和fail
這樣的回調方法。 數組
以後的版本返回的是一個deferred對象用promise
方法包裝過的對象,能夠進行鏈式操做,使用鏈式寫法後,代碼可讀性大大提升。promise
若是要在一個ajax請求成功後再執行別的回調函數,該怎麼辦呢? 直接在加在後面就能夠了:服務器
$.ajax('test.jspx') .done(function(data) { // 請求成功了 // TODO }).fail(function(error) { // 請求失敗了 // TODO }).done(function(data) { // 請求成功了 // then TODO });
這種寫法能夠支持無數個回調函數,這寫回調函數將按照添加順序依次執行。異步
若是一個回調函數須要在幾個ajax請求都成功後才能執行該怎麼辦呢?是否是很差控制呢?其實jQuery提供了這樣一個方法$.when()
它能夠接收任意個deferred對象,只有全部的deferred對象都狀態都爲成功時才執行done
回調,不然執行fail
回調。
$.when($.ajax("test1.html"), $.ajax("test2.html")) .done(function() { // 兩個操做都請求都成功 // TODO }).fail(function() { // 任意一個失敗 // TODO });
上面代碼中,只有兩個請求都成功後纔會執行done
回調,只要有一個失敗就執行fail
回調。
deferred對象的最大優勢,就是它把這一套回調函數接口,從ajax操做擴展到了全部操做。也就是說,任何一個操做----不論是ajax操做仍是本地操做,也不論是異步操做仍是同步操做----均可以使用deferred對象的各類方法,指定回調函數。
var wait = function() { var dtd = $.Deferred(); // 新建一個deferred對象 var tasks = function() { alert("執行完畢!"); dtd.resolve(); // 此操做完成後改變deferred對象的執行狀態 }; setTimeout(tasks, 5000); return dtd; }; // 綁定回調函數 $.when(wait()) .done(function() { alert("執行成功了!"); }) .fail(function() { alert("出錯啦!"); });
上面代碼的中的wait
方法模擬了一個很耗時的操做,以後給這個操做指定了回調函數done
和fail
。一旦wait
執行完畢就會當即調用done
這個回調函數。
上面已經對deferred
對象有所瞭解了,下面介紹一下deferred
對象的經常使用方法。
在jQuery的deferred
對象中,規定了它有三種狀態:
未完成 繼續等待
已完成 當即調用done回調
已失敗 當即調用fail回調
resolve
方法的做用就是設置deferred
對象狀態爲已完成,deferred對象馬上調用done()方法指定的回調函數
reject
方法的做用是設置deferred
對象狀態爲已失敗,deferred對象馬上調用fail()方法指定的回調函數
done
方法用於指定deferred
對象狀態爲已完成時的回調函數。
done
方法用於指定deferred
對象狀態爲已失敗時的回調函數。
then
方法接收一到三個參數,分別指定deferred
對象狀態爲已成功、已失敗和繼續等待的回調函數。
always
方法用於指定deferred
對象狀態爲已成功或已失敗時的回調函數。
即不管這個deferred
對象是成功仍是失敗,只要執行完畢都會調用此方法指定的回調。
因爲此方法指定的回調函數的參數是不肯定的(好比ajax請求成功和失敗返回的信息不一樣,成功時爲返回的數據,失敗則爲錯誤信息),最好只使用它的行爲,而不檢查其參數。若是要執行的操做和參數有關,請顯示地指定done
和fail
回調。以下所示:
$.ajax('test1.html') .always(function() { // 無論請求是否成功,只要請求完畢就執行 // TODO console.log('已請求完畢,狀態未知'); }); $.ajax('test1.html') .done(function(data) { // 請求成功時執行 // TODO console.log('請求已成功,返回數據爲:'); console.log(data); }) .fail(function(error) { // 請求失敗時執行 // TODO console.log('請求已失敗,錯誤信息:'); console.log(error.status, error.statusText); });
progress
方法用於指定deferred
對象狀態爲等待中的回調函數。可是它僅在deferred
對象生成了進度通知時纔會被調用。
請看下面例子:
var wait = function() { var dtd = $.Deferred(); // 新建一個deferred對象 var tasks = function() { alert("執行完畢!"); dtd.resolve(); // 此操做完成後改變deferred對象的執行狀態 }; setTimeout(tasks, 5000); return dtd; }; // 綁定回調函數 $.when(wait()) .done(function() { alert("執行成功了!"); }) .fail(function() { alert("出錯啦!"); }) .progress(function(){ console.log("正在執行中..."); // 此處不會有任何輸出 });
上面雖然指定了progress回調,可是卻爲沒有任何做用的緣由是因爲在deferred
對象沒有生成進度通知,因此其不會被調用。
想要progress回調能執行,須要在deferred
對象上調用此回調。notify
方法的做用就是根據給定的 args參數 調用Deferred對象上進行中的progress回調。
var wait = function() { var dtd = $.Deferred(); // 新建一個deferred對象 var i = 1, timer, percent; // 記錄進度 var tasks = function() { if (i == 11) { alert("執行完畢!"); dtd.resolve(); // 此操做完成後改變deferred對象的執行狀態 } else { percent = (i * 500) / 5000 * 100 + '%'; dtd.notify(percent); // 調用progress回調 i++; setTimeout(tasks, 500); } }; setTimeout(tasks, 1000); return dtd; }; // 綁定回調函數 $.when(wait()) .done(function() { alert("執行成功了!"); }) .fail(function() { alert("出錯啦!"); }) .progress(function(data) { console.log('執行中,已完成', data); }); // 執行中,已完成 10% // 執行中,已完成 20% // 執行中,已完成 30% // 執行中,已完成 40% // 執行中,已完成 50% // 執行中,已完成 60% // 執行中,已完成 70% // 執行中,已完成 80% // 執行中,已完成 90% // 執行中,已完成 100% // 以後彈出 執行完畢!和 執行成功了!
這個方法給上傳文件或者耗時操做生成進度條提供了一種可能。
jQuery3.0以上版本對
when
方法作了大幅調整。向promise/A+
靠齊,上面的寫法中notify
是觸發不了when中的progress回調的,須要使用promise
來給對象部署deferred接口或使用$.Deferred()
傳入函數名。
簡而言之,3.0以以上版本中,上面代碼中progress回調是不會進去的,應使用如下寫法:
一、
promise
給一個對象部署Deferred
接口:
var dtd = $.Deferred(); // 新建一個deferred對象 var wait = function(dtd) { var i = 1, timer, percent; // 記錄進度 var tasks = function() { if (i == 11) { alert("執行完畢!"); dtd.resolve(); // 此操做完成後改變deferred對象的執行狀態 } else { percent = (i * 500) / 5000 * 100 + '%'; dtd.notify(percent); // 調用progress回調 i++; setTimeout(tasks, 500); } }; setTimeout(tasks, 1000); }; // 在wait對象上部署Deferred接口,此後就能夠直接在wait上使用deferred對象promise後的方法了 dtd.promise(wait); // 在wait對象上使用deferred對象的方法指定回調。 wait.done(function() { alert("執行成功了!"); }) .fail(function() { alert("出錯啦!"); }) .progress(function(data) { console.log('執行中,已完成', data); }); // 執行 wait(dtd);
二、使用
$.Deferred
傳入函數名:
var wait = function(dtd) { var i = 1, timer, percent; // 記錄進度 var tasks = function() { if (i == 11) { alert("執行完畢!"); dtd.resolve(); // 此操做完成後改變deferred對象的執行狀態 } else { percent = (i * 500) / 5000 * 100 + '%'; dtd.notify(percent); // 調用progress回調 i++; setTimeout(tasks, 500); } }; setTimeout(tasks, 1000); return dtd; }; // 綁定回調函數 $.Deferred(wait) .done(function() { alert("執行成功了!"); }) .fail(function() { alert("出錯啦!"); }) .progress(function(data) { console.log('執行中,已完成', data); });
promise
方法的做用是在原來的deferred對象上返回另外一個deferred對象,後者只開放與改變執行狀態無關的方法(好比done()方法和fail()方法),屏蔽與改變執行狀態有關的方法(好比resolve()方法和reject()方法),從而使得執行狀態不能被改變。
var dtd = $.Deferred(); // 新建一個Deferred對象 var wait = function(dtd) { var tasks = function() { alert("執行完畢!"); dtd.resolve(); // 改變Deferred對象的執行狀態 }; setTimeout(tasks, 5000); return dtd; }; $.when(wait(dtd)) .done(function() { alert("執行成功!"); }) .fail(function() { alert("出錯啦!"); }); dtd.reject(); // 改變狀態爲失敗,將當即觸發fail 5s後完成再出發done
若是咱們把deferred對象定義在了函數外部,那麼咱們設置deferred對象的狀態就會致使調用對應的回調。上面代碼中,最後調用reject
方法,會致使當即調用了fail回調,5s以後又彈出執行完畢和執行成功。這將會致使沒必要要的混亂。使用promise
方法就是一種解決方案。(以前寫的將var dtd = $.Deferred()
放在函數內部,使得外部訪問不到也是一種解決方案)。
var dtd = $.Deferred(); // 新建一個Deferred對象 var wait = function(dtd) { var tasks = function() { alert("執行完畢!"); dtd.resolve(); // 改變Deferred對象的執行狀態 }; setTimeout(tasks, 5000); return dtd.promise(); // 返回promise對象 }; var d = wait(dtd); // 新建一個d對象,改成對這個對象進行操做 $.when(d) .done(function() { alert("哈哈,成功了!"); }).fail(function() { alert("出錯啦!"); }); d.resolve(); // d.resolve is not a function 通過promise後沒有resolve方法了
咱們看一下Deferred
對象和它promise
以後的區別。
promise
返回的對象上已經去掉了和改變狀態有關的方法。notify
和notifyWith
是調用progress回調,resolve
和reject
用於設置其狀態,帶with的方法可指定上下文環境。
此方法還能夠接收Object類型的參數,
deferred.promise()
會將事件綁定到該參數上,而後返回該對象,而不是建立一個新的對象。 這個方法能夠用於在已經存在的對象上綁定 Promise 行爲的狀況。示例以下:
var dtd = $.Deferred(); // 生成Deferred對象 var wait = function(dtd) { var tasks = function() { alert("執行完畢!"); dtd.resolve(); // 執行完畢後改變Deferred對象的執行狀態 }; setTimeout(tasks, 5000); }; // 在wait對象上部署Deferred接口,此後就能夠直接在wait上使用deferred對象promise後的方法了 dtd.promise(wait); // 在wait對象上使用deferred對象的方法指定回調。 wait.done(function() { alert("哈哈,成功了!"); }) .fail(function() { alert("出錯啦!"); }); // 執行 wait(dtd);
$.Deferred()
除了建立一個deferred對象以外,能夠接受一個函數名(注意,是函數名)做爲參數,$.Deferred()
所生成的deferred對象將做爲這個函數的默認參數。
var wait = function(dtd) { var tasks = function() { alert("執行完畢!"); dtd.resolve(); }; setTimeout(tasks, 5000); }; $.Deferred(wait) .done(function() { alert("執行成功!"); }) .fail(function() { alert("執行失敗!"); });
接收一個或多個deferred對象做爲參數,爲其指定回調函數。
以前咱們介紹了,then
方法接一到三個參數,分別指定deferred
對象狀態爲已成功、已失敗和繼續等待的回調函數。
除此以外,then
還能夠傳遞遲延對象。咱們知道,deferred
能夠鏈式操做的緣由是其返回的還是deferred
對象。then
中的回調函數若是沒有返回新的deferred
對象時,將依然使用最開始的那個deferred
對象,若是在其也行返回一個deferred
對象時,以後的操做將被轉移到在回調函數中return出來的新的deferred
對象,從而進行傳遞。
咱們來看一個例子:
有5個js文件,其內容以下:
// perosn.js var Person = function(name){ this.name= name; }; // person.prototype.js if (Person) { Person.prototype.showName = function() { return this.name; }; } else { console.error('Person is undefined!'); } // zs.js var zs = new Person('張三'); // ls.js var ls = new Person('李四'); // introduce.js var introduceEachOther = function() { console.log('張三:你好,我叫' + zs.showName()); console.log('李四:你好,我叫' + ls.showName()); };
文件內容都很是簡單,僅作演示使用,可是其中的依賴關係很是明顯,person.prototype.js依賴於perosn.js,zs.js和ls.js依賴於person.js,introduce.js依賴於其餘全部的js。
要分步加載,並保證可用,就必須保證在加載當前js時,其依賴的js已經加載完畢。
使用傳統的寫法,勢必會嵌套多層,不只邏輯很差處理,並且可讀性不好。
咱們用then
演示一下如何來傳遞遞延對象來完成這個操做。
$.ajax({ url: 'person.js', dataType: 'script' }).then(function() { console.log(Person); // person.js已經加載成功 console.log('person.js已經加載成功'); return $.ajax({ url: 'person.prototype.js', dataType: 'script' }); }).then(function(data) { // 這裏的data 上一步請求文件的內容 是person.prototype.js而非person.js console.log(data); console.log(Person.prototype.showName); console.log('person.prototype.js已經加載成功'); // person.prototype.js 已經加載 return $.when($.ajax({ url: 'zs.js', dataType: 'script' }), $.ajax({ url: 'ls.js', dataType: 'script' })); }).then(function(){ // zs.js 和 ls.js 都加載完成 console.log(zs,ls); console.log('zs.js和zs.js已經加載成功'); return $.ajax({ url: 'introduce.js', dataType: 'script' }); },function(){ console.log('zs.js or ls.js failed'); }).then(function(){ // 到此前面的全部資源都是加載成功的 console.log('introduce.js已經加載成功,且其依賴資源都加載成功了,可使用了'); introduceEachOther(); });
以上處理順序只是舉例演示,只要保證person.js最早加載,introduce.js最後加載,person.prototype.js在person.js以後便可。ls.js、zs.js與person.prototype.js的順序能夠調整。
咱們每次在then
的第一個回調中返回了而一個新的遞延對象,以後的操做就是基於你返回的那個遞延對象了,第二個then
回調中輸出的data證實了這一點。(若是沒有return
出一個新的遞延對象的話,依然使用以前的那個。)
本文是照着下面的文章加上本身的學習體會書寫的。