jQuery的Deferred对象,Promise对象
Ajax
Ajax
Ajax是实现异步操作的技术,用于创建快速动态网页的技术。可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
传统的网页(不使用 AJAX)如果需要更新内容,必须重载整个网页页面。
没有Deferred对象的ajax
$.ajax({
url: 'xxx',
success: function(){}
error: function() {}
})
如果在要实现多个ajax的操作,那么就要在success: function(){}里面再次嵌套$.ajax({});方法。不可以链式操作。如果多层回调的话,那么代码不美观,可读性也差。
基于这样的背景,jQuery1.5之后引入了Deferred对象,Deferred对象就是jQuery的回调函数的解决方案。
Deferred对象和promise对象都是用来管理异步操作,是对ajax过程的一种抽象。从程序的角度来减少回调嵌套。
Deferred对象
jQuery1.5引入Deferred对象之后,可以实现链式操作写法。
$.ajax('/xxx').success(function(){}).fail(function(){})
它定义了两种基本状态:resolved和 rejected,然后定义了三个方法success,fail,always来定义当deferred对象从初始状态转换到两种基本状态时候应该调用哪 些函数。最后提供了reject/rejectWith,resolve/resolveWith来改变deferred对象的状态,以便出发对应的回调函数。
如果ajax操作成功后,除了原来的回调函数,我还想再运行一个回调函数,直接把它加在后面就行了。
$.ajax("test.html")
.done(function(){ alert("哈哈,成功了!");} )
.fail(function(){ alert("出错啦!"); } )
.done(function(){ alert("第二个回调函数!");} );
$.when()
对Promise对象也适用。
如果想为多个操作添加一个回调函数,Deferred对象也提供了相应的方法,也就是$.when()方法。
$.when($.ajax("test1.html"), $.ajax("test2.html"))
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
这段代码的意思是,先执行两个操作$.ajax("test1.html")和$.ajax("test2.html"),如果成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。
deferred对象的最大优点,就是它把这一套回调函数接口,从ajax操作扩展到了所有操作。也就是说,任何一个操作,不管是ajax操作还是本地操作,也不管是异步操作还是同步操作----都可以使用deferred对象的各种方法,指定回调函数。
比如,
var wait = function(){
var tasks = function(){
alert("执行完毕!");
};
setTimeout(tasks,5000);
};
$.when(wait())
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
但是,when的参数只能是deferred对象,对wait进行改写。
var dtd = $.Deferred(); // 新建一个deferred对象
var wait = function(dtd){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变deferred对象的执行状态
};
setTimeout(tasks,5000);
return dtd.promise();
};
这里有两个地方需要注意。首先,最后一行不能直接返回dtd,必须返回dtd.promise()。 原因是jQuery规定,任意一个deferred对象有三种执行状态----未完成,已完成和已失败。如果直接返回dtd,$.when()的默认执行 状态为"已完成",立即触发后面的done()方法,这就失去回调函数的作用了。dtd.promise()的目的,就是保证目前的执行状态----也就 是"未完成"----不变,从而确保只有操作完成后,才会触发回调函数。
其次,当操作完成后,必须手动改变Deferred对象的执行状态,否则回调函数无法触发。dtd.resolve()的作用,就是将dtd的执行状态从"未完成"变成"已完成",从而触发done()方法。
最后别忘了,修改完wait之后,调用的时候就必须直接传入dtd参数。
$.when(wait(dtd))
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
$.then()
对Promise对象也适用。
用于给一个deferred对象添加监听器。该方法接受两个参数:
deferred.then( doneCallbacks, failCallbacks )
doneCallbacks: 一个函数,或者是一组函数,在deferred被实现时调用
failCallbacks: 一个函数,或者是一组函数,在deferred被拒绝时调用
例子:
Js代码
- $.get("test.php").then(
- function(){ alert("$.get succeeded"); },
- function(){ alert("$.get failed!"); }
- );
deferred.then()提供了一种方便的写法,让一个deferred在实现或者被拒绝时的监听函数可以写在一个api中。
Promise对象
Promise对象其实就是deferred对象的特例,因为Promise对象不能更改异步状态,而deferred对象可以。
promise方法已经被混入deferred对象了,但是deferred对象的一些方法没有在promise中,主要就是触发状态转换和函数回调的几个方法比如reject/rejectWith。
所以promise能做的 deferred都能做到,但是deferred能触发状态转换确实promise做不到的。所以使用promise方法返回promise对象可以防止 被其他人改变自己的状态,他们只能通过promise来注册回调,但是不能通过promise来改变状态。
promise对象常见的方法有三个:done,fail,then。
其它的方法就不要去记了,jQuery这里的接口方法太多了,在我看来挺啰嗦的,就跟早期的事件方法绑定一样,live,delegate,bind,最终不是都归为on来管了么。
代码示例,如下:
1.DOM使用Promise方法:
var box=$('#box');
box.promise().done(function(ele){
console.log(ele);//jQuery box
});
2.Ajax使用Promise方法(默认返回一个Promise对象,所以可以不必显式调用Promise方法):
$.post('/',{}).done(function(data){
console.log('请求成功');
}).fail(function(){
console.log('请求错误');
});
Deferred对象和Promise对象
promise对象常见的方法有三个:done,fail,then。
对于deferred对象呢,也就是使用$.Deferred()方法,以及$.when()等方法创造出来的对象,有如下的常用方法:
- resolve,reject,notify;
- done,fail,progress;
另外还有promise、then和always方法。
之所以这么排版,是因为他们是对应的,也就是说:resolve方法会触发done的回调执行,reject会触发fail的回调,notify会触发progress的回调。
直接看代码:
var wait = function(ms) {
var dtd = $.Deferred();
setTimeout(dtd.resolve, ms);
// setTimeout(dtd.reject, ms);
// setTimeout(dtd.notify, ms);
return dtd.promise(); //此处也可以直接返回dtd
};
wait(2500).done(function() {
console.log('haha,师太,你可让老衲久等了');
}).fail(function() {
console.log('失败了');
}).progress(function(res) {
console.log('等待中...');
});
我们看到了,上面的代码中,在wait函数中,返回的是个Promise对象,而不是deferred对象。
要知道,Promise对象是没有resolve,reject,notify等方法的,也就意味着,你无法针对Promise对象进行状态更改,只能在done或fail中进行回调配置。所以,你如果这么调用wait(2500).resolve()将会报错,因为wait(2500)返回的是个Promise对象,不存在resolve方法。
但是,这么做,有个好处,我们把dtd这个deferred对象放在了wait函数中,作为了局部变量,避免了全局的污染;进一步通过Promise方法,转化dtd这个deferred对象为Promise对象,避免了函数wait外部可能发生的状态更改(假如我们确实有这个需求)。