上篇文章中讲到,使用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。