JQuery.Defferred()是基于Promise/A规范,因为JQuery本身的设计风格,在之前的版本并没有完全遵照Promise/A规范,在JQuery3.0中实现了完全的Promise实现,并且在$.ajax中移除了success、error、complete方法,改为对应的done、fail、always方法
需要注意的是在低于1.5.0版本的JQuery中,ajax返回的是XHR对象,而在高于1.5.0的版本中返回的则是deferred对象
在JQuery1.5、1.6版本中Deffered对象耦合比较严重,推荐使用1.8以上版本
deferred对象可以用来更加方便的操作异步函数,正常情况下异步函数执行的时候是没有先后顺序的,可能我们有B函数需要依赖A函数中的某一个参数,但是B函数却会在A函数之前先执行完毕了,这种情况在NodeJS,gulp等都是非常常见的,而且更糟糕的是如果需要的某一个数据是使用AJAX来获取的,我们再外部调用数据很有可能会报错,因为AJAX是异步的
var data;
$.get('api/data', function(resp) {
data = resp.data;
});
doSomethingFancyWithData(data);
可能我们会想到将Ajax设置为同步来解决问题,但这毫无疑问会带来阻塞,更加正确的做法是为它指定回掉函数,也就是事先规定,一旦函数运行结束,应该调用那些函数,简单理解的话deferred对象就是JQuery的回调函数解决方案,deferred对象正像它的英文名含义一样,就是延迟到未来某个点再执行,而这个点就是异步函数执行完毕的时间
我们首先在代码中打印一下$.Deferredd对象
//Object
// always:ƒ()
// done:ƒ()
// fail:ƒ()
// notify:ƒ()
// notifyWith:ƒ(context, args)
// pipe:ƒ(/* fnDone, fnFail, fnProgress */)
// progress:ƒ()
// promise:ƒ(obj)
// reject:ƒ()
// rejectWith:ƒ(context, args)
// resolve:ƒ()
// resolveWith:ƒ(context, args)
// state:ƒ()
// then:ƒ(/* fnDone, fnFail, fnProgress */)
// __proto__:Object
接下来我们开始看一下他们的大概用法
在之前我们使用ajax传统写法是
$.ajax({
url: "test.html",
success: function(){
...
},
error:function(){
...
}
});
而在1.5.0之后ajax返回的是一个deferred对象,我们就可以使用deferred的写法来写
$.ajax("test.html")
.done(function(){...})
.fail(function(){...})
.done(function(){...});
在JQuery中的Deferred对象上有一个pipe方法,该方法可以在执行done方法前对返回数据进行处理,在处理完成后可以通过renturn返回给done函数作为参数
$.get("url_1")
.pipe(res=>{
console.log(res); //{data: Array(12), status: "success"}
return res.data[0];
})
.done(res=> {
console.log(res); //{id: null, name: "普工", code: "1", typeid: null, sortOrder: null, …}
})
.pipe(res=>{
return res.name;
})
.done(res=> {
console.log(res); //普工
})
可以看到,done方法相当于success方法,fail相当于error方法,采用链式写法可以极大的提高代码的可读性,而且允许我们自由添加多个回调函数,添加的回调函数将会按照添加顺序执行,而且允许我们为多个事件指定一个回调函数,这是传统写法做不到的
$.when(
$.ajax({url: "./02.txt"}),
$.ajax({url: "./03.txt"})
)
.always(function () {
console.log("hello");
})
.done(function (x, y) { //
console.log(x);
console.log(y);
//x,y都是数组,分别对应第一个和第二个ajax请求的返回值
})
.fail(function (e) {
throw new Error("请检查路径");
})
$.when()是jQuery提供的一个新方法,该方法会在两个操作都成功的情况下运行done指定的回调函数,如果有一个失败或都失败了就执行fail指定的回调函数,不论失败或成功都会执行always方法,需要注意一点,如果使用when方法,那么需要传入的参数必须都是deferred对象,否则的话起不到回调函数的作用
var wait = function(){
var tasks = function(){
alert("执行完毕")
};
setTimeout(tasks,2000);
};
$.when(wait())
.done(function(){ alert("成功")})
.fail(function(){ alert("失败")});
//done中的方法会立即执行,因为wait不是deferred对象,$.when无效
那么我们接下来看一下如何将一个普通函数变成一个deferred对象
在这之前,我们先了解一个概念,执行状态,JQuery中规定,deferred对象有三种执行状态:
正在执行、已完成和已失败
如果执行状态是已完成,那么deferred对象立刻调用done()方法指定的回调函数;如果是已失败,调用fail()中的回调函数,如果执行状态是未完成,则继续等待,或者定义了progress(),就调用该方法中指定的回调函数,在之前使用ajax时,JQuery会根据返回结果,自动改变自身的执行状态,但是在其它不是deferred对象的函数中需要我们手动指定执行状态,这个时候就需要我们用到$.deferred上的方法
def.resolved()表示将def的状态变为已完成,从而触发done方法,在resolve中可以传入参数,该参数将会在done中指定的函数的参数中被接收
当然还有对应的将状态变更为未完成的方法 reject触发fail方法
但是在上面的方法存在一个问题,那就是def是一个全局对象,所以它的执行状态是可以在外部改变的
那么执行这段代码的时候我们会发现现在会立即执行done方法,在编译时我们在尾部添加的代码会优先于wait方法执行,所以在执行时会立即执行done方法,为了避免这种情况,JQuery提供了deferred.promise()方法,该方法作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法,屏蔽与改变状态有关的方法,从而使得执行状态不能被改变
当然,最佳的方法是将def设置为函数wait内的局部变量,这样外部就无法获取了,也就不能改变状态了
var wait = function(dtd){
var def = $.Deferred(); //在函数内部,新建一个deferred对象
.....
//同上案例代码
return dtd.promise(); //当然,只返回dtd就可以了,为了保险我们还是将它转换为promise对象
};
$.when(wait())... //同上案例代码
另一只防止执行状态被外部改变的方法,是使用deferred对象的构建函数$.Deferred()
JQuery规定,$.Deferred()可以接收一个函数名作为参数,所生成的deferred对象将作为这个函数的默认参数
我们也可以直接在wait函数上部署deferred接口
当然还有一个方法,deferred.then(),该方法将done和fail结合到了一起,如果值传入一个参数,效果等同于done
$.when($.ajax( "/main.php" ))
.then(successFunc, failureFunc )
在jQuery 1.8之前,then()只是.done().fail()写法的语法糖,两种写法是等价的。在jQuery 1.8之后,then()返回一个新的deferred对象,而done()返回的是原有的deferred对象。如果then()指定的回调函数有返回值,该返回值会作为参数,传入后面的回调函数
$.get("url_1")
.then(function (res) {
return $.get("url_2")
})
.then(function (res) {
console.log(res); //返回$.get("url_2")的结果
})
以上代码是需要特别注意的,如果我们使用的是done方法,那么返回的会一直是$.get("url_1")的结果
$.get("url_1")
.done(function (res) {
return $.get("url_2")
})
.done(function (res) {
console.log(res); //返回$.get("url_1")的结果
})