浅析 jQuery deferred 对象

什么是 deferred 对象

deferred 对象是 jQuery 为操作的回调函数提供的解决方案。deferred 对象提供了链式操作来对操作进行回调处理。

deferred 对象引入了“执行状态”这个概念,一个 deferred 对象的执行状态分为三种

  1. 未完成(unresolve)
  2. 已完成(resolved)
  3. 已失败(rejected)

通过 deferred 对象的 resolve,reject 方法来改变执行状态从而调用与该状态绑定的回调函数(们)。

注意:deferred 对象状态一旦被 resolve 或 reject 便不再次修改。也就是说,resolve 或者 reject 方法只能调用一次。多次调用只执行一次(jQuery 的实现是这样的,不代表所有的 deferred 实现都是如此,此行为不可预测,可能导致崩溃)

deferred 有什么好处

  1. 避免了层层嵌套的“回调地狱”
  2. 提供了可以安排顺序的回调处理方式
  3. 链式操作可以让回调处理更加的自由,可添加任意个数的回调函数

deferred 用法

定义一个 deferred 对象

1
2
3
4
5
6
7
8
9
10
11
12
var wait = function(dtd) {
var dtd = $.Deferred();
setTimeout(function() {
this.time = 1;
dtd.resolve();
}, 1000);
dtd.progress(processing);
for (var i = 0; i < 10; i++) {
dtd.notify()
}
return dtd.promise();
};

提供 proimise()

promise() 方法实际上是在原来的 deferred 基础上返回一个只开放不能改变执行状态的方法(如 done() 和 fail() )的 deferred 对象,使得执行状态无法被非执行者改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var wait = function(dtd) {
var dtd = $.Deferred();
setTimeout(function() {
dtd.resolve();
}, 1000);

return dtd.promise();
};
$.when(wait())
.done(function() {
console.log("哈哈,成功了!");
})
.fail(function() {
console.log("出错啦!");
})

为同一操作提供多种状态的回调处理

done 处理 resolved 状态
fail 处理 rejected 状态

他们都可以接受参数,通过 resolve() 或者 reject() 方法将数据传递给回调方法。

1
2
3
$.when(wait())
  .done(function(){ alert("哈哈,成功了!"); })
  .fail(function(){ alert("出错啦!"); });

控制回调处理的先后顺序

这里分为两种情况:

  1. 回调处理是同步的

直接使用多个 then 或者 done 方法的链接就可以实现

1
2
3
4
5
6
7
8
9
10
11
12
13
$.when(wait())
.done(function() {
console.log("1 return!");
})
.done(function(time) {
console.log("2 return!");
})
.done(function(time) {
for (var i = 0; i < 1000000000; i++) {

}
console.log("3 return!");
})

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
$.when(wait())
.then(function() {
console.log("1 return!");
})
.then(function(time) {
console.log("2 return!");
})
.then(function(time) {
for (var i = 0; i < 1000000000; i++) {

}
console.log("3 return!");
})

需要注意的是:使用 done 方法的场景是几种回调的处理是在同一层次上的,done 方法不能通过 return 来将值传递给后续的 done 方法。
then 方法的使用场景是几个回调在不同层次上处理返回的数据,then 方法中的参数可以通过 return 语句将处理过的数据传递到下一级 then() 中的回调函数中。

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$.when(wait())
.then(function() {
console.log("1 return!");
return 1;
})
.then(function(time) {
console.log("2 return!"+time);
return 2;
})
.then(function(time) {
console.log("3 return!"+time);
})
/**
* 输出:
* 1 return!
* 2 return1!
* 3 return2!
*/
  1. 回调处理是异步的

这时需要使用 then 方法,并在每个 then 方法中传入 deferred 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$.when(wait())
.then(function() {
var dtd = $.Deferred();
setTimeout(function() {
console.log("1 return!");
dtd.resolve();
}, 1000);
return dtd.promise()
})
.then(function(time) {
var dtd = $.Deferred();
setTimeout(function() {
console.log("2 return!");
dtd.resolve();
}, 1000);
return dtd.promise()
})
.then(function(time) {
var dtd = $.Deferred();
setTimeout(function() {
console.log("3 return!");
dtd.resolve();
}, 1000);
return dtd.promise()
})

为多个操作提供统一的回调处理

将多个操作合并返回,只有当全部操作都 resolved 之后才会执行 done 函数。

1
2
3
$.when(wait1(), wait2()
  .done(function(){ alert("哈哈,成功了!"); })
  .fail(function(){ alert("出错啦!"); });

为普通操作提供回调函数

使用 deferred 对象不仅可以为异步过程提供回调,也可以为同步过程提供回调。

1
2
3
4
5
6
var wait2 = function(dtd) {
var dtd = $.Deferred();
syncOp()
dtd.progress(processing);
return dtd.promise();
};

在处理过程中返回信息

使用 deferred 对象的 progress() 方法指定回调函数。在过程执行中调用 deferred 对象的 notify() 方法可以调用这个回调函数。

使用场景如:网络请求数据之前先取缓存数据,取到数据后通知控制器更新视图。然后发起网络请求,请求返回后 resolve() 将数据传递给控制器再次更新视图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var wait = function(dtd) {
var dtd = $.Deferred();

//TODO: 取网络数据
dtd.progress(processing);
dtd.notify()//通知控制器更新视图
//模拟发起网络请求
setTimeout(function() {
this.time = 1;
dtd.resolve();//返回数据后 resolve()
}, 1000);

return dtd.promise();
};

$.when(wait())
  .done(function(){ alert("哈哈,成功了!,用网络数据再次更新视图"); })
  .fail(function(){ alert("出错啦!"); });
function processing() {
console.log('取缓存成功,更新视图');
}