上篇文章中講到,使用jquery的ajax方法操做ajax請求,會受到回調函數嵌套的問題。固然,jquery團隊也發現了這個問題,在2011年,也就是jquery 1.5版本以後,jQuery.Deferred對象爲解決這類問題應運而出。以後,zapto等框架也推出相同api的deferred對象,來進行異步操做。javascript
在jquery 1.5 版本以後,ajax請求的內部實現被重寫。$.ajax方法返回的再也不是一個jqXHR對象,而是一個Deferred對象。java
使用jquery 1.5版本以後的代碼,能夠用下面的方法進行一次ajax請求。jquery
// 前提引入jquery var fetchData = function (url) { return $.ajax({ type: 'get', url: url }); }
這樣一次請求的內容就已經完成,$.ajax返回一個$.Deferred對象,那麼咱們就可使用$.Deferred對象的api進行一些異步操做。git
出於篇幅,這裏只簡單進行介紹.done
,.fail
,.then
和$.when
這三個方法。github
對於每個$.Deferred對象來講,實例有多個方法,其中done方法表明異步完成時執行的方法,fail表明異步失敗時執行的方法,這兩個方法同時仍舊返回一個$.Deferred對象的實例。ajax
繼續上面的ajax操做,咱們能夠這樣寫成功和失敗的回調:segmentfault
// fetchData 接上 fetchData() //執行函數返回一個Deferred對象實例 .done() //接受一個函數,ajax請求成功調用 .fail() //接受一個函數,ajax請求失敗調用 .done() //第二個成功狀態的回調方法 .fail()
一樣的對於.then
方法,接受兩個函數,第一個表明成功時執行的方法,第二個表明失敗時的執行方法。一樣的,它也返回一個deferred對象實例。意味着也能進行連綴調用。api
fetchData() .then(successFn, errorFn) //第一次回調 .then(successFn, errorFn) //第二次回調
內部實現上,.done 和 .fail 都是基於 .then實現的數組
fetchData() fetchData() .done(successFn) <===> .then(successFn, null) .fail(errorFn) <===> .then(null, errorFn)
對於多個ajax同時請求,共同執行同一個回調函數這一點上,jquery有一個$.when方法,接受多個Deferred對象實例,同時執行。框架
var fetchData = function (url) { return $.ajax({ type: 'get', url: url }); } var fetchData1 = fetchData('/path/to/data1'); var fetchData2 = fetchData('/path/to/data2'); $.when(fetchData1, fetchData2, function (data1, data2) { // fetchData1 響應爲data1 // fetchData2 響應爲data2 })
完美解決了開發中的異步問題。
上面的$.ajax只是在$.deferred對象上封裝了一層ajax操做。實際上,真正的$.Deferred對象是這樣調用的:
function printA () { var deferred = new $.Deferred(); setTimeout(function () { console.log('A'); deferred.resolve(' is done.'); }, 1000); return deferred; } function printB (msg) { var deferred = new $.Deferred(); setTimeout(function () { console.log('B' + msg); deferred.resolve(); }, 1000); return deferred; } printA() .then(printA) .then(printB)
每一個函數維護一個Deferred對象,在每個具備異步操做的函數執行成功後,指示全局deferred對象執行下一個函數,達到異步的效果。
新建完成$.Deferred實例deferred以後,調用deferred.resolve()表明成功完成響應,deferred.reject()即表明調用失敗響應。
詳細解釋源碼請參見另外一篇文章,這裏咱們主要寫一下這種調用方式實現的tiny版。
首先咱們寫一個Callback對象維護咱們的回調函數隊列
var Callbacks = function () { function Callbacks () { this.callbacks = []; } Callbacks.prototype.add = function (fn) { this.callbacks.add(fn); return this; } Callbacks.prototype.fire = function () { var len = this.callbacks.length; if(len) { this.callbacks.unshift()(); } } return Callbacks; }
這段代碼邏輯很簡單,Callbacks對象有兩個方法,分別是add和fire,調用add則向當前的callbacks數組內新增一個function。fire方法,則從Callbacks中提取最前的一個callback,並執行它。
對於Deferred對象,咱們至少須要resolve和reject兩個方法。進行成功和失敗的調用。而且可以進行鏈式調用。
var Deferred = function () { this.successCbs = new Callbacks(); this.errorCbs = new Callbacks(); } Deferred.prototype.then = function (successCb, errorCb) { this.successCbs.add(successCb); this.errorCbs.add(errorCb); return this; } Deferred.prototype.resolve = function () { this.successCbs.fire(); return this; } Deferred.prototype.reject = function () { this.errorCbs.fire(); return this; }
這樣簡單完成以後,咱們新建一個Deferred實例,就可以經過鏈式調用的方式進行異步操做。
var deferred = new Deferred(); function printA() { setTimeout(function () { console.log('A'); deferred.resolve(); }, 1000); return deferred; } function printB() { setTimeout(function () { console.log('B'); deferred.resolve(); }, 1000); } printA() .then(printB) .then(printA)
一樣的,咱們能夠封裝一個自制tiny-Deferred對象的tiny-ajax方法。
var ajax = function (options) { var xhrOptions = { type: options.type || 'get', url: options.url || '/default/path', async: options.async || true }; var deferred = new Deferred(); var xhr = new XHRHttpRequest(); xhr.open(xhrOptions.type, xhrOptions.url, xhrOptions.async); xhr.onload = function (result) { deferred.resolve(result); } xhr.onerror = function () xhr.send(); return deferred; }
具體源代碼開源在Github上,歡迎pr和issuses。