Deferred对象是由$.Deferred构造的,$.Deferred被实现为简单工厂模式。
它用来解决JS中的异步编程,它遵循 Common Promise/A 规范。实现此规范的还有 when.js 和 dojo。
$.Deferred作为新特性首次出现在版本1.5中,这个版本利用Deferred又完全重写了Ajax模块。
$.Deferred在jQuery代码自身四处被使用,分别是promise方法、DOM ready、Ajax模块、动画模块。
这里以版本1.8.3分析,由于1.7后$.Callbacks从Deferred中抽离出去了,目前版本的deferred.js代码不过150行,而真正$.Deferred的实现只有100行左右。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
jQuery.extend({
Deferred:
function
( func ) {
var
tuples = [
// action, add listener, listener list, final state
[
"resolve"
,
"done"
, jQuery.Callbacks(
"once memory"
),
"resolved"
],
[
"reject"
,
"fail"
, jQuery.Callbacks(
"once memory"
),
"rejected"
],
[
"notify"
,
"progress"
, jQuery.Callbacks(
"memory"
) ]
],
...
// All done!
return
deferred;
},
// Deferred helper
when:
function
( subordinate
/* , ..., subordinateN */
) {
var
i = 0,
resolveValues = core_slice.call( arguments ),
length = resolveValues.length,
....
return
deferred.promise();
}
});
|
$.Deferred的实现
- 创建三个$.Callbacks对象,分别表示成功,失败,处理中三种状态
- 创建了一个promise对象,具有state、always、then、primise方法
- 通过扩展primise对象生成最终的Deferred对象,返回该对象
$.when的实现
- 接受若干个对象,参数仅一个且非Deferred对象将立即执行回调函数
- Deferred对象和非Deferred对象混杂时,对于非Deferred对象remaining减1
- Deferred对象总数 = 内部构建的Deferred对象 + 所传参数中包含的Deferred对象
- 所传参数中所有Deferred对象每当resolve时remaining减1,直到为0时(所有都resolve)执行回调
这就是$.Deferred和$.when的全部了,各个方法及使用稍后介绍。
代码阅读中会发现then和when方法的实现最难理解,看多次,后感回味无穷,非常巧妙。then内部会用到不同寻常的递归,when用到了计数,每次异步成功后减一,直到为0后表示全部异步操作成功,这时才可执行回调。
上面提到Deferred里有3个$.Callbacks的实例,Deferred自身则围绕这三个对象进行更高层次的抽象。以下是Deferred对象的核心方法
- done/fail/progress 是 callbacks.add,将回调函数存入
- resolve/reject/notify 是 callbacks.fire,执行回调函数(或队列)
下面举一些示例看看如何使用Deferred对象。
一、done/resolve
1
2
3
4
5
6
7
8
|
function
cb() {
alert(
'success'
)
}
var
deferred = $.Deferred()
deferred.done(cb)
setTimeout(
function
() {
deferred.resolve()
}, 3000)
|
在HTTP中表示后台返回成功状态(如200)时使用,即请求成功后可执行成功回调函数。
二、fail/reject
1
2
3
4
5
6
7
8
|
function
cb() {
alert(
'fail'
)
}
var
deferred = $.Deferred()
deferred.fail(cb)
setTimeout(
function
() {
deferred.reject()
}, 3000)
|
在HTTP中表示后台返回非成功状态时使用,即请求失败后可执行失败回调函数。
三、progress/notify
1
2
3
4
5
6
7
8
|
function
cb() {
alert(
'progress'
)
}
var
deferred = $.Deferred()
deferred.progress(cb)
setInterval(
function
() {
deferred.notify()
}, 2000)
|
在HTTP中表示请求过程中使用,即请求过程中不断执行回调函数。这可用在文件上传时的loading百分比或进度条。
四、链式操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
function
fn1() {
alert(
'success'
)
}
function
fn2() {
alert(
'fail'
)
}
function
fn3() {
alert(
'progress'
)
}
var
deferred = $.Deferred()
deferred.done(fn1).fail(fn2).progress(fn3)
// 链式操作
setTimeout(
function
() {
deferred.resolve()
//deferred.reject()
//deferred.notify()
}, 3000)
|
这样可以很方便了添加成功,失败,进度回调函数。
五,便利函数then,一次添加成功,失败,进度回调函数
1
2
3
4
5
6
7
8
9
10
11
|
function
fn1() {
alert(
'success'
)
}
function
fn2() {
alert(
'fail'
)
}
function
fn3() {
alert(
'progress'
)
}
var
deferred = $.Deferred()
deferred.then(fn1, fn2, fn3)
|
调用then后还可以继续链式调用then添加多个不同回调函数,这个then也正是jQuery对 Common Promise/A 的实现。
六、使用always方法为成功,失败状态添加同一个回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var
deferred = $.Deferred()
deferred.always(
function
() {
var
state = deferred.state()
if
( state ===
'resolved'
) {
alert(
'success'
)
}
else
if
(state ===
'rejected'
) {
alert(
'fail'
)
}
})
setTimeout(
function
() {
deferred.resolve()
//deferred.reject()
}, 3000)
|
回调函数中可以使用deferred.state方法获取异步过程中的最终状态,这里我调用的是deferred.resolve,因此最后的状态是resolved,表示成功。
七、when方法保证多个异步操作全部成功后才回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
function
fn1() {
alert(
'done1'
)
}
function
fn2() {
alert(
'done2'
)
}
function
fn3() {
alert(
'all done'
)
}
var
deferred1 = $.Deferred()
var
deferred2 = $.Deferred()
deferred1.done(fn1)
deferred2.done(fn2)
$.when(deferred1, deferred2).done(fn3)
setTimeout(
function
() {
deferred1.resolve()
deferred2.resolve()
}, 3000)
|
先后弹出了done1、done2、all done。 如果setTimeout中有一个reject了,fn3将不会被执行。
八、deferred.promise()方法返回只能添加回调的对象,这个对象与$.Deferred()返回的对象不同,只能done/fail/progress,不能resolve/reject/notify。即只能调用callbacks.add,没有callbacks.fire。它是正统Deferred对象的阉割版。
有了Deferred,我们使用jQuery书写ajax的风格可以这样了
1
2
3
|
$.ajax(url)
.done(success)
.fail(fail)
|
看似和以前比较也没什么优点,但它还可以添加多个回调
1
2
3
4
5
|
$.ajax(url)
.done(success1)
.done(success2)
.fail(fail2)
.fail(fail2)
|
1.5之前的则不行
如果多个请求完成后才算成功,1.5之前的是无法解决的,现在则可以用$.when搞定
1
2
3
|
var
ajax1 = $.ajax(url1)
var
ajax2 = $.ajax(url2)
$.when(ajax1, ajax2).done(success)
|
如果项目中有一些异步问题不妨用用Derferred。