学习 Deferred 对象

Deferred 对象

Deferred 对象,是 jQuery 的一个特别的对象,用来解决回调函数的问题。

1.5 开始,$.ajax 方法也开始使用这个对象。1.5- 时,$.ajax() 方法返回的是 XHR 对象。之后 1.5+ 开始,返回的是一个 Deferred 对象。因此,原先设置在对象中的 success 和 error 属性方法不再使用,取而代之的是一些回调方法,包括 done 和 fail。而且调用方式是通过链式调用来实现,如:

$.ajax()
.done()
.fail()

对某个操作指定多个回调函数

如上面的 ajax 的栗子,在 ajax 操作结束后有绑定了两个回调函数。但其实,可以连续绑定多个回调函数的,直接在后面继续调用即可。

$.ajax()
.done()
.fail()
.done()

为多个操作指定相同的回调函数

这是 deferred 对象的一个特点,也是其优点。它允许我们为多个事件指定共同的回调函数。
在下面这个栗子中,用到的是 $.when() 方法:

$.when( $.ajax('test1.html'), $.ajax('test2.html') )
.done(function(){
alert('success');
})
.fail(function(){
alert('fail');
})
.done(function(){
alert('success again');
})

Deferred 使用

deferred 对象使用的范围不仅仅只是 ajax(),其他普通操作也适用。不管是 ajax 操作还是本地操作,也不管是异步操作还是同步操作,都可以通过使用 deferred 对象来指定回调函数。
那么,怎么给具体操作指定回调函数呢?这就利用了 deferred 对象的状态值。
deferred 对象的状态值有:pending、resolved(已完成)、rejected(已失败)。
状态值通过使用 deferred 对象的属性方法——resolve()、reject() 来改变状态值。

var dfd = $.Deferred(); // 创建一个 deferred 对象
// 在这个函数中设置任意自定义操作,
// 并且在操作完成后,改变 deferred 对象的状态值,以此来触发回调函数
var binding = function( dfd ){
var foo = function(){
alert('action finished');
dfd.resolve(); // 改变状态值为“已完成”
}
setTimeout(foo, 3000);
return dfd;
}
// 使用 when() 方法来指定回调函数
$.when( binding )
.done(function(){
alert('success');
})
.fail(function(){
alert('fail');
})

在上面的栗子中,dfd.resolve() 是必须的,不然的话会 done() 会立即执行。
但是有个问题,假如 dfd.resolve() 是在外部执行的话,done() 方法就会在 foo 操作完成之前就执行,这样就没有了回调函数的意义。
因此,就需要对这种情况进行预防。

防止执行状态被外部改变的几种方案

deferred.promise()

promise() 方法的作用是在原来对象的基础上,返回一个另一个 deferred 对象。这个对象中,屏蔽了和改变执行状态值有关的方法(如 resolve() 和 reject() ),只开发和状态值无关的方法。这样使得状态被隐藏起来不能被改变。

var dfd = $.Deferred(); // 创建一个 deferred 对象
// 在这个函数中设置任意自定义操作,
// 并且在操作完成后,改变 deferred 对象的状态值,以此来触发回调函数
var binding = function( dfd ){
var foo = function(){
alert('action finished');
dfd.resolve(); // 改变状态值为“已完成”
}
setTimeout(foo, 3000);
return dfd.promise(); // 返回新的 deferred 对象
}
// 使用 when() 方法来指定回调函数
var d = binding(dfd);
$.when( )
.done(function(){
alert('success');
})
.fail(function(){
alert('fail');
})
d.resolve(); // 报错
dfd.reject(); // 可以执行

(但是有个问题,上面栗子中,dfd 还是一个全局变量,那么可以跳过 d 对象直接操作 dfd,这样使用 promise 不是没有意义了?)

通过改良后,得到一个更好的方案:

// 在这个函数中设置任意自定义操作,
// 并且在操作完成后,改变 deferred 对象的状态值,以此来触发回调函数
var binding = function(){
var dfd = $.Deferred(); // 在函数内部创建 deferred 对象
var foo = function(){
alert('action finished');
dfd.resolve(); // 改变状态值为“已完成”
}
setTimeout(foo, 3000);
return dfd.promise(); // 返回新的 deferred 对象
}
// 使用 when() 方法来指定回调函数
var d = binding();
$.when(d)
.done(function(){
alert('success');
})
.fail(function(){
alert('fail');
})
d.resolve(); // 报错

这样,dfd 这原来的 deferred 对象就是个局部变量,外界不能访问,不会被改变状态值。而且,binding 返回的是 promise 后的对象,也不能改变状态值。

直接使用构造函数 $.Deferred()

自定义的操作不变,还是 binding,但是我们直接把它传入 $.Deferred()。

var binding = function(dfd){
var foo = function(){
alert('action finished');
dfd.resolve(); // 改变状态值为“已完成”
}
setTimeout(foo, 120000);
}
$.Deferred(binding)
.done(function(){
alert('success');
})
.fail(function(){
alert('fail');
})

直接在操作的函数上部署 deferred 接口

var dfd = $.Deferred();
var binding = function(){
var foo = function(){
alert('action finished');
dfd.resolve(); // 改变状态值为“已完成”
}
setTimeout(foo, 3000);
}
dfd.promise(binding);
binding
.done(function(){
alert('success');
})
.fail(function(){
alert('fail');
})
binding(dfd)
d.resolve();

其他属性方法

.state()

返回 deferred 对象的状态值

.always()

只要 deferred 对象状态变化,就会触发执行

.progress() & .notify()

progress() 可以让我们指定一个回调函数;
而 notify() 方法则是,当它被调用时,progress() 指定的回调函数就会被执行。

其用意是提供一个接口,使得在非同步操作执行过程中,可以执行某些操作,比如定期返回进度条的进度。

.then()

概述

then() 方法可以一次性设置多个回调函数。接受的参数有三个,分别是 resolved、rejected 的回调方法,以及 progress() 指定的回调函数。其中,后两者是可选。

deferred.then( doneFilter [, failFilter] [, progressFilter] )

返回值

值得注意的是,在 jQuery 1.8-,then() 和 .done().fail() 是等价的。在 1.8+,then() 返回一个新的 promise 对象。而 done() 返回的是原有的 deferred 对象。如果 then() 指定的回调函数有返回值的话,该返回值会作为参数,传入后面的回调函数

var defer = $.Deferred();
defer.done(function(a,b){
return a*b;
})
.done(function(result){
console.log('result: ' + result);
})
.then(function(a,b){
return a*b;
})
.done(function(result){
console.log('result: ' + result);
})
.then(function(a,b){
return a*b;
})
.done(function(result){
console.log('result: ' + result);
});
defer.resolve(2, 3);

1.8- 的结果:

result = 2
result = 2
result = 2

1.8+ 的结果:

result = 2
result = 6
result = NaN

利用 then() 的特点

既然 then() 指定的回调函数会修改返回值,那么,我们可以利用这个特性,在调用其他回调函数时,对之前操作返回的值进行处理。


(其余的坑等接触了 jQuery 源码的相关知识后再填)