Deffered是Jquery中的一個很是重要的對象,從1.5版本以後,Jquery中的ajax操做都基於Deffered進行了重構,這個對象的處理模式就像其餘Javascript框中的Promise異步模式同樣,它表明一個潛在的、長時間運行但沒必要返回完成操做的結果,與等待並阻塞瀏覽器進程直到完成操做相比,Deffered返回的是一個承諾異步執行結果的對象,這個承諾能夠有返回值,也能夠沒有,瀏覽器被釋放出來作其餘事情,直到這個返回結果被使用到。Deffered的原理是給異步請求過程當中狀態的變化註冊回調函數,實現鏈式調用,如對象的then函數;統一對這些回調函數的結果進行管理控制,以面對多個請求的須要,如Jquery中的when函數。html
下面用一個簡單的例子看看Deffered對象是如何工做的:jquery
1 function BeginRequest() { 2 //定義deferred對象 3 var def = $.Deferred(); 4 //建立XMLHttpRequest對象 5 var request = new XMLHttpRequest(); 6 //配置一個異步請求 7 request.open("GET", "../textPage/httpScript.ashx"); 8 //定義請求狀態變化的處理過程 9 request.onreadystatechange = function() { 10 if (request.readyState === 4 && request.status === 200) { 11 def.resolve(request.responseText); 12 } 13 else if (request.status === 404) { 14 def.reject(); 15 } 16 } 17 //設置請求頭 18 request.setRequestHeader("content-type", "text/plain"); 19 //開啓一個請求 20 request.send(null); 21 //返回deferred對象 22 return def; 23 }
咱們先經過JQuery中的Deferred()函數建立一個deferred對象,而後在異步請求中根據請求的狀態分別註冊對象的resolve和reject函數,最後返回這個deferred對象,接下來能夠進行鏈式調用:ajax
1 BeginRequest().then(function(s) { 2 console.log("success."); 3 }, function() { 4 console.log("failed."); 5 });
上面調用了deferred對象的then函數,這個函數有兩個函數參數,第一個是異步結果成功的時候執行,第二個是失敗的時候執行。數組
這裏須要介紹一下deferred對象的三種執行狀態:未完成、已完成、已失敗。若是是已完成(resolve),deferred對象會當即觸發done()函數指定的回調函數;若是是已失敗(reject),deferred對象會當即觸發fail()函數指定的回調函數;若是是未完成,即等待過程當中,則繼續等待或者deferred對象觸發process()(jquery1.7版本及以上)函數指定的回調函數。上面的then函數傳遞了兩個回調函數分別對完成和失敗兩種狀態進行處理,並且deferred對象的狀態能夠經過調用對象中resolve()和reject()函數改變,調用分別觸發done()和fail()指定的回調函數,故上述調用部分能夠改寫爲:promise
1 BeginRequest().done(function(s) { 2 console.log("success."); 3 }).fail(function() { 4 console.log("failed."); 5 });
能夠看出,deferred對象的引入給程序的異步執行帶來了極大的便利,更加簡潔,可讀性更高。這在JQuery Ajax請求中獲得了及廣泛的應用,在1.5版本之前,Ajax請求返回的是XHR對象,其成功及差錯處理方式以下:瀏覽器
1 $.ajax({ 2 //要請求的url 3 url: "../textPage/httpScript.ashx", 4 //請求成功後調用的回調函數 5 success: function(result) { 6 console.log("success."); 7 }, 8 //請求失敗後調用的回調函數 9 error: function(e) { 10 console.log("failed."); 11 } 12 });
傳統的ajax請求接收一個無類型對象爲參數,對象中指定了請求的url及回調函數,當回調過程當中嵌套ajax請求的話,這個過程看起來很不清晰。在1.5及之後的版本中,ajax返回的對象再也不是XHR類型,而是deferred對象,且會自動觸發其狀態的改變,故新的ajax請求能夠改寫爲:安全
1 $.ajax("../textPage/httpScript.ashxs").then(function() { 2 console.log("success."); 3 }, function() { 4 console.log("failed."); 5 });
這裏使用了then方法,固然,也能夠調用fail和done函數實現。異步
上面對JQuery中deferred對象的使用有了初步的瞭解,接下來進行進一步學習:函數
deferred對象的做用範圍學習
正是由於deferred的狀態能夠經過其對象調用resolve和reject函數來動態改變,因此在程序流程中咱們不但願其狀態被意外的改變而形成錯誤,下面代碼是很好的證實:
1 function BeginRequest() { 2 //定義deferred對象 3 var def = $.Deferred(); 4 //建立XMLHttpRequest對象 5 var request = new XMLHttpRequest(); 6 //配置一個異步請求 7 request.open("GET", "../textPage/httpScript.ashx"); 8 //定義請求狀態變化的處理過程 9 request.onreadystatechange = function() { 10 if (request.readyState === 4 && request.status === 200) { 11 //正常處理過程被延時5秒鐘 12 setTimeout(function() { 13 def.resolve("the request is complete."); 14 }, 5000); 15 } 16 else if (request.status === 404) { 17 def.reject(); 18 } 19 } 20 //設置請求頭 21 request.setRequestHeader("content-type", "text/plain"); 22 //開啓一個請求 23 request.send(null); 24 //返回deferred對象 25 return def; 26 } 27 28 //保存異步操做結果的deferred對象,以做他用 29 var tempDef = BeginRequest().done( 30 function(msg) { 31 if (arguments.length <= 0) { 32 //若是是意外改變 33 console.log("valid call."); 34 } 35 else { 36 //成功後改變 37 console.log(msg); 38 } 39 }).fail( 40 function() { 41 console.log("failed."); 42 }); 43 //意外改變deferred的狀態爲已完成 44 tempDef.resolve();
代碼中將請求成功後的執行過程人爲的延遲了5秒,以模仿耗時操做,在調用的時候保存了deferred對象以備他用,可是程序意外的經過這個被保存的對象更改了其狀態,執行上面的代碼,輸出」valid call「而不是」the request is complete .「。
可見,deferred對象的直接傳遞存在必定的安全和不肯定的風險,爲了不這種狀況,有三種方式能夠解決:
1. 儘可能在同一個函數或者模塊中使用deferred對象及其相關操做。
2. 在使用deferred對象時儘可能使用鏈式調用,不要暴露傳遞中的deferred對象給其餘模塊。
3. 爲此,JQuery專門爲deferred定義了一個promise方法,該方法也返回一個deferred對象,但這個對象再也不包含引發狀態改變的函數,如:resolve、reject、resolveWith等,這種方式最優。
對於JQuery中的Promise函數,個人理解是爲了保持異步執行狀態,防止不肯定狀態改變而定義的。借上例,咱們將BeginRequest()函數的最後一行改寫爲:
1 //返回deferred的安全對象 2 return def.promise();
如此,再次執行上面的過程,程序果斷拋出異常:
5秒後輸出正常路徑下的結果,可見,deferred對象的狀態沒有被改變。這裏有一篇很好的文章,能夠參考一下。
同一個異步操做指定多個回調函數
也就是說,在異步操做返回的deferred對象上能夠綁定多個回調函數(done、fail、then都可),幸運的是:done、fail、then函數返回的都是對同一個deferred對象的引用,故能夠對同一個deferred對象執行多個done(或者fail或者then)處理過程,這些處理過程一次順序執行,以done爲例,代碼以下:
1 $.ajax("../textPage/httpScript.ashxs") 2 .done(function() { 3 console.log("success."); 4 }) 5 .done(function() { 6 console.log("第二次處理."); 7 }) 8 .done(function() { 9 console.log("第三次處理"); 10 }); 11 //……
大部分實際需求均不會如此使用,但對於流程性比較強的處理過程,能夠如此來分步執行,使實現過程更加簡潔,特別是在動畫處理的過程當中。
大多數時候,咱們只須要在done的回調函數中執行響應處理操做便可,可是偶爾須要針對不一樣的狀況做出不一樣的處理,如何作呢?嗯,其實done等異步處理函數中指定的回調函數是能夠添加參數的,單個異步請求中,回調函數有三個參數
,第一個參數是異步結果,第二個對象是請求狀態,第三個是結果的deferred對象,代碼以下:
1 $.ajax("../textPage/httpScript.ashx") 2 //成功時執行此回調函數 3 .done( 4 function(result, status, def) { 5 console.log(result.toString()); 6 console.log(status.toString()); 7 console.log(typeof def); 8 } 9 ) 10 //失敗時執行此回調函數 11 .fail( 12 function(result, status, def) { 13 console.log("failed."); 14 } 15 );
輸出以下:
爲多個異步操做指定統一的回調處理
在實際需求中,咱們會遇到不少異步操做,若是業務邏輯要求在某些異步操做均完成後再進行其餘操做,那麼咱們須要對這些異步操做進行等待,直到全部都完成,即爲多個異步操做定義統一的回調函數來對成功和失敗進行管理,傳統的異步方法是作不到的。在JQuery中,when函數能夠很好的很方便的解決這一問題,來看下面的代碼:
1 $.when($.ajax("../textPage/httpScript.ashx"), $.ajax("../textPage/Pictrue.aspx")) 2 //成功時執行此回調函數 3 .done( 4 function() { 5 console.log("success.") 6 } 7 ) 8 //失敗時執行此回調函數 9 .fail( 10 function() { 11 console.log("failed."); 12 } 13 );
$.when()函數返回的是deferred對象,故上面對該返回值用函數done()、fail()分別處理成功及失敗的狀況(then函數也能夠實現),和其餘不一樣的是when函數中咱們執行了兩個異步請求,它的執行過程須等待全部起步請求執行完畢,才能進行done或者fail的處理流程,而且,只有當when函數中的全部異步操做均成功才能觸發done函數中指定的回調函數,其中一個或者多個失敗則會觸發fail函數中指定的回調函數。
有趣的是,咱們能夠在done或者fail中進行詳細跟蹤每一個請求的結果,這須要對done和fail中定義的回調函數添加參數,改寫以下:
1 $.when($.ajax("../textPage/httpScript.ashx"), $.ajax("../textPage/Pictrue.aspx")) 2 //成功時執行此回調函數 3 .done( 4 function(r1, r2) { 5 console.log("success.") 6 } 7 ) 8 //失敗時執行此回調函數 9 .fail( 10 function(r1, r2) { 11 console.log("failed."); 12 } 13 );
通過測試,發現上述陰影部分所標記的參數分別表明when中每一個請求的結果對象,對這些參數進行監控調試,其內容以下:
由上圖能夠看出,r1和r2均是長度爲3的數組,以r1爲例,數組的第一個元素是異步操做的結果,第二個元素是異步操做狀態,第三個元素是當前異步操做返回的deferred結果對象。經驗證,r1和r2分別是then中兩個請求的結果對象,如此咱們在爲多個異步操做統一回調函數的同時,也能夠根據不一樣操做的返回結果給用戶以不一樣的反饋,這能夠經過給回調函數添加參數得到。
不侷限於異步ajax請求
JQuery中的deferred對象不只應用於ajax異步請求,對於一些複雜耗時的腳本操做也是能夠的,它提供了對異步操做結果的一個未來結果,用以對異步操做的一個管理,查看下面的 代碼:
1 var handle = function() { 2 //建立deferred對象 3 var def = $.Deferred(); 4 //內部函數 5 function test() { 6 alert("over."); 7 //改變執行狀態 8 def.resolve(); 9 } 10 //模仿某個複雜耗時的操做,模擬須要5秒,而後再執行函數test 11 setTimeout("test()", 5000); 12 //返回deferred對象; 13 return def.promise(); 14 } 15 16 $.when(handle) 17 .done( 18 function() { 19 console.log("success."); 20 } 21 ) 22 .fail( 23 function() { 24 console.log("failed."); 25 } 26 );
總結:JQuery中的deferred對象爲了js腳本的異步過程提供了一種結果處理方式,它不改變原有的異步過程,以更加簡潔、更加易讀的方式管理並執行異步操做,從原理上來講,只是更加完善的完成異步操做而已。