Deffered是Jquery中的一个非常重要的对象,从1.5版本之后,Jquery中的ajax操作都基于Deffered进行了重构,这个对象的处理模式就像其他Javascript框中的Promise异步模式一样,它代表一个潜在的、长时间运行但不必返回完成操作的结果,与等待并阻塞浏览器进程直到完成操作相比,Deffered返回的是一个承诺异步执行结果的对象,这个承诺可以有返回值,也可以没有,浏览器被释放出来做其他事情,直到这个返回结果被使用到。Deffered的原理是给异步请求过程中状态的变化注册回调函数,实现链式调用,如对象的then函数;统一对这些回调函数的结果进行管理控制,以面对多个请求的需要,如Jquery中的when函数。
下面用一个简单的例子看看Deffered对象是如何工作的:
function BeginRequest() {
//定义deferred对象
var def = $.Deferred();
//创建XMLHttpRequest对象
var request = new XMLHttpRequest();
//配置一个异步请求
request.open("GET", "../textPage/httpScript.ashx");
//定义请求状态变化的处理过程
request.onreadystatechange = function() {
if (request.readyState === 4 && request.status === 200) {
def.resolve(request.responseText);
}
else if (request.status === 404) {
def.reject();
}
}
//设置请求头
request.setRequestHeader("content-type", "text/plain");
//开启一个请求
request.send(null);
//返回deferred对象
return def;
}
我们先通过JQuery中的Deferred()函数创建一个deferred对象,然后在异步请求中根据请求的状态分别注册对象的resolve和reject函数,最后返回这个deferred对象,接下来可以进行链式调用:
BeginRequest().then(function(s) {
console.log("success.");
}, function() {
console.log("failed.");
});
上面调用了deferred对象的then函数,这个函数有两个函数参数,第一个是异步结果成功的时候执行,第二个是失败的时候执行。
这里需要介绍一下deferred对象的三种执行状态:未完成、已完成、已失败。如果是已完成(resolve),deferred对象会立即触发done()函数指定的回调函数;如果是已失败(reject),deferred对象会立即触发fail()函数指定的回调函数;如果是未完成,即等待过程中,则继续等待或者deferred对象触发process()(jquery1.7版本及以上)函数指定的回调函数。上面的then函数传递了两个回调函数分别对完成和失败两种状态进行处理,而且deferred对象的状态可以通过调用对象中resolve()和reject()函数改变,调用分别触发done()和fail()指定的回调函数,故上述调用部分可以改写为:
BeginRequest().done(function(s) {
console.log("success.");
}).fail(function() {
console.log("failed.");
});
可以看出,deferred对象的引入给程序的异步执行带来了极大的便利,更加简洁,可读性更高。这在JQuery Ajax请求中得到了及普遍的应用,在1.5版本以前,Ajax请求返回的是XHR对象,其成功及差错处理方式如下:
$.ajax({
//要请求的url
url: "../textPage/httpScript.ashx",
//请求成功后调用的回调函数
success: function(result) {
console.log("success.");
},
//请求失败后调用的回调函数
error: function(e) {
console.log("failed.");
}
});
传统的ajax请求接收一个无类型对象为参数,对象中指定了请求的url及回调函数,当回调过程中嵌套ajax请求的话,这个过程看起来很不清晰。在1.5及以后的版本中,ajax返回的对象不再是XHR类型,而是deferred对象,且会自动触发其状态的改变,故新的ajax请求可以改写为:
$.ajax("../textPage/httpScript.ashxs").then(function() {
console.log("success.");
}, function() {
console.log("failed.");
});
这里使用了then方法,当然,也可以调用fail和done函数实现。
上面对JQuery中deferred对象的使用有了初步的了解,接下来进行进一步学习:
deferred对象的作用范围
正是因为deferred的状态可以通过其对象调用resolve和reject函数来动态改变,所以在程序流程中我们不希望其状态被意外的改变而造成错误,下面代码是很好的证明:
function BeginRequest() {
//定义deferred对象
var def = $.Deferred();
//创建XMLHttpRequest对象
var request = new XMLHttpRequest();
//配置一个异步请求
request.open("GET", "../textPage/httpScript.ashx");
//定义请求状态变化的处理过程
request.onreadystatechange = function() {
if (request.readyState === 4 && request.status === 200) {
//正常处理过程被延时5秒钟
setTimeout(function() {
def.resolve("the request is complete.");
}, 5000);
}
else if (request.status === 404) {
def.reject();
}
}
//设置请求头
request.setRequestHeader("content-type", "text/plain");
//开启一个请求
request.send(null);
//返回deferred对象
return def;
} //保存异步操作结果的deferred对象,以作他用
var tempDef = BeginRequest().done(
function(msg) {
if (arguments.length <= 0) {
//如果是意外改变
console.log("valid call.");
}
else {
//成功后改变
console.log(msg);
}
}).fail(
function() {
console.log("failed.");
});
//意外改变deferred的状态为已完成
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为例,代码如下:
$.ajax("../textPage/httpScript.ashxs")
.done(function() {
console.log("success.");
})
.done(function() {
console.log("第二次处理.");
})
.done(function() {
console.log("第三次处理");
});
//……
大部分实际需求均不会如此使用,但对于流程性比较强的处理过程,可以如此来分步执行,使实现过程更加简洁,特别是在动画处理的过程中。
大多数时候,我们只需要在done的回调函数中执行响应处理操作即可,但是偶尔需要针对不同的情况作出不同的处理,如何做呢?嗯,其实done等异步处理函数中指定的回调函数是可以添加参数的,单个异步请求中,回调函数有三个参数
,第一个参数是异步结果,第二个对象是请求状态,第三个是结果的deferred对象,代码如下:
$.ajax("../textPage/httpScript.ashx")
//成功时执行此回调函数
.done(
function(result, status, def) {
console.log(result.toString());
console.log(status.toString());
console.log(typeof def);
}
)
//失败时执行此回调函数
.fail(
function(result, status, def) {
console.log("failed.");
}
);
输出如下:
为多个异步操作指定统一的回调处理
在实际需求中,我们会遇到很多异步操作,如果业务逻辑要求在某些异步操作均完成后再进行其他操作,那么我们需要对这些异步操作进行等待,直到所有都完成,即为多个异步操作定义统一的回调函数来对成功和失败进行管理,传统的异步方法是做不到的。在JQuery中,when函数可以很好的很方便的解决这一问题,来看下面的代码:
$.when($.ajax("../textPage/httpScript.ashx"), $.ajax("../textPage/Pictrue.aspx"))
//成功时执行此回调函数
.done(
function() {
console.log("success.")
}
)
//失败时执行此回调函数
.fail(
function() {
console.log("failed.");
}
);
$.when()函数返回的是deferred对象,故上面对该返回值用函数done()、fail()分别处理成功及失败的情况(then函数也可以实现),和其他不同的是when函数中我们执行了两个异步请求,它的执行过程须等待所有起步请求执行完毕,才能进行done或者fail的处理流程,并且,只有当when函数中的所有异步操作均成功才能触发done函数中指定的回调函数,其中一个或者多个失败则会触发fail函数中指定的回调函数。
有趣的是,我们可以在done或者fail中进行详细跟踪每个请求的结果,这需要对done和fail中定义的回调函数添加参数,改写如下:
$.when($.ajax("../textPage/httpScript.ashx"), $.ajax("../textPage/Pictrue.aspx"))
//成功时执行此回调函数
.done(
function(r1, r2) {
console.log("success.")
}
)
//失败时执行此回调函数
.fail(
function(r1, r2) {
console.log("failed.");
}
);
经过测试,发现上述阴影部分所标记的参数分别代表when中每个请求的结果对象,对这些参数进行监控调试,其内容如下:
由上图可以看出,r1和r2均是长度为3的数组,以r1为例,数组的第一个元素是异步操作的结果,第二个元素是异步操作状态,第三个元素是当前异步操作返回的deferred结果对象。经验证,r1和r2分别是then中两个请求的结果对象,如此我们在为多个异步操作统一回调函数的同时,也可以根据不同操作的返回结果给用户以不同的反馈,这可以通过给回调函数添加参数获得。
不局限于异步ajax请求
JQuery中的deferred对象不仅应用于ajax异步请求,对于一些复杂耗时的脚本操作也是可以的,它提供了对异步操作结果的一个将来结果,用以对异步操作的一个管理,查看下面的 代码:
var handle = function() {
//创建deferred对象
var def = $.Deferred();
//内部函数
function test() {
alert("over.");
//改变执行状态
def.resolve();
}
//模仿某个复杂耗时的操作,模拟需要5秒,然后再执行函数test
setTimeout("test()", 5000);
//返回deferred对象;
return def.promise();
} $.when(handle)
.done(
function() {
console.log("success.");
}
)
.fail(
function() {
console.log("failed.");
}
);
总结:JQuery中的deferred对象为了js脚本的异步过程提供了一种结果处理方式,它不改变原有的异步过程,以更加简洁、更加易读的方式管理并执行异步操作,从原理上来说,只是更加完善的完成异步操作而已。