最近做项目,调用后端接口,需要等到2个ajax返回再执行另一个ajax,首先想到的是使用promise,但是考虑到浏览器的兼容和编译问题,只能放弃。最后使用了标志位来解决这个问题,在调用之前设置一个flag,返回结果后改变这个flag值,一层层的判断。虽然方法笨,但解决了问题,项目结束后,看到了$.when().done().then(),觉得这是一个比较好的解决方法。
介绍$.when().done().then()之前,先说一个deferred对象,这个是jquery1.5.0引入的新功能。deferred翻译过来就是延迟,所以deferred对象的作用就是“延迟”到某个时间点执行,可以用来解决jquery的回调函数。
一、jquery1.5.0版本以后,$.ajax()返回的是deferred对象,可以进行链式操作。如下所示:
$.ajax("test.html")
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
可以看出,.done()相当于success方法,.fail相当于error方法。
二、可以为同一个操作指定多个回调函数
在上个栗子中,如果还想再执行一个回调函数怎么办?
简单,直接在后面接着
.done(function () {console.log(1);})
可以随心所欲添加多个回调函数,按照添加顺序执行回调函数。
三、为多个操作指定同一个回调函数
重头戏来了,也就是我开篇说到的,等到2个ajax的结果,再执行回调,这个时候就可以用到$.when()了。
$.when($.ajax('test1.html'), $.ajax('test2.html'))
.done(function () {console.log('成功了');})
.fail(function () {console.log('失败了');})
回调函数会等两个ajax都执行完后才执行,如果都成功会执行done里的回调,如果有一个失败或者都失败,执行fail里的回调。
四、任何操作都可以使用deferred对象的方法,指定回调函数
deferred对象最大的优点就是,把这套回调函数接口,从ajax操作扩展到了所有操作,无论是同步还是异步操作。如下,一个等待很久的wait操作:
var wait = function () {
var tasks = function(){
console.log('执行完毕');
}
setTimeout(tasks, 5000);
}
指定回调函数
$.when(wait())
.done(function () {console.log('成功了');})
.fail(function () {console.log('失败了')})
运行程序,你会发现,直接执行了done函数,这是为什么呢?
原因在于,$.when()的参数必须是一个deferred对象,现在对wait()进行改写。
var dtd = $.Deferred();//新建一个deferred对象
var wait = function () {
var tasks = function () {
console.log('执行完毕');
dtd.resolve();//改变deferred对象的执行状态
}
setTimeout(tasks, 5000);
return dtd;
}
现在wait已经是一个deferred对象了,可以使用上面的回调函数方法,wait执行完后,执行done函数。
五、deferred.resolve()和deferred.reject()
deferred对象有三个执行状态:未完成,已完成,已失败。若是已完成,则调用done函数;若是已失败,则调用fail函数;若是未完成,则一直等待,或者指定progress()方法指定回调函数(jq1.7添加)。ajax操作,deferred对象会根据返回结果,自动改变状态,但wait操作不会,需要我们手动改变。resolve方法就是把状态从未完成变成已完成,而reject就是从未完成变成已失败。
六、deferred.promise()
在第四条中,我们创建了一个deferred对象,把这个赋给了一个全局变量,这样存在一个隐患,我可以在外部改变状态,例如:
var dtd = $.Deferred();//新建一个deferred对象
var wait = function () {
var tasks = function () {
console.log('执行完毕');
dtd.resolve();//改变deferred对象的执行状态
}
setTimeout(tasks, 5000);
return dtd;
}
$.when(wait())
.done(function () {console.log('成功了');})
.fail(function () {console.log('失败了')})
dtd.resolve();
如上所示,这时done函数会立即执行,没有达到我们想要的效果。所以jq提供了deferred.promise()方法,即deferred对象又返回了另外一个deferred对象。这个新的deferred对象只开放与改变执行状态无关的方法(done或fail方法),不开放改变执行状态的方法(resolve或reject方法)。改变一下上面的代码:
var wait = function (dtd) {
var dtd = $.Deferred();//内部新建一个deferred对象
var tasks = function () {
console.log('执行完毕');
dtd.resolve();//改变deferred对象的执行状态
}
setTimeout(tasks, 5000);
return dtd.promise();//返回promise对象
}
$.when(wait())
.done(function () {console.log('成功了');})
.fail(function () {console.log('失败了')})
dtd.resolve();//此时,这个语句说无效的
如上,可以把deferred对象变成一个局部变量
七、$.Deferred(函数名)
除了上面说的在内部新建deferred对象,还有一种方法,就是使用deferred对象的建构函数$.Deferred(),直接把函数名作为参数传入,$.Deferred()生成的对象默认作为这个函数的参数。即:
$.Deferred(wait)
.done(function () {console.log('成功');})
.fail(function () {console.log('失败')})
八、在wait对象上部署Deferred接口
除上述两种方法,还有一种就是在wait对象上直接部署Deferred接口,如下
var dtd =$.Deferred();//生成deferred对象
var wait = function (dtd) {
var tasks = function () {
console.log('执行完毕');
dtd.resolve();
}
setTimeout(tasks, 5000);
}
dtd.promise(wait);//在wait对象上部署Deferred接口
wait.done(function () {console.log('成功')})
.fail(function () {console.log('失败')})
wait(dtd);
用以上这种方法,wait可以直接调用done(),fail()。
九、总结
1.$.Deferred();生成一个deferred对象
2.deferred.done();指定成功时的回调操作
3.deferred.fail();指定失败时的回调操作
4.deferred.resolve();手动改变执行状态从未完成到已完成,从而触发done()方法
5.deferred.reject();手动改变执行状态从未完成到已失败,从而触发fail()方法
6.deferred.promise();无参数时,返回一个新的deferred对象,该对象的执行状态无法手动改变;有参数时,作用为在参数上部署Deferred接口
7.$.when();为多个操作指定同一个回调函数
8.deferred.then(success, fail);接收两个参数,第一个参数为成功时的回调操作,第二个参数为失败时的回调操作,相当于done(),fail()的综合写法。第二个参数可以省略,只有一个参数时,默认为成功时的回调操作
9.deferred.always();无论成功还是失败,最后都会执行的操作
10.deferred.state();确认deferred对象的当前状态
11.deferred.isResolved();确认deferred对象是否已被解决
12.deferred.isRejected();确认deferred对象是否已被拒绝,即已失败
13.deferred.catch();deferred对象失败时,调用添加的操作