3.9 jQuery的Deferred对象 3.9.1 jQuery的异步调用

3.9 jQuery的Deferred对象

在进行JavaScript开发的过程中,经常会遇到要执行耗时任务的情况,该任务可能是远程Ajax 调用,也可能是本地的耗时操作,总之不能立即有结果。遇到这种情况,通常就需要采用回调函数来监听该耗时操作的执行情况,当耗时操作执行完成后,自动激发相应的回调函数。jQuery为解决这类问题提供了Deferred对象。

3.9.1 jQuery的异步调用

jQuery以前的版本中,当我们使用$.ajax()方法进行Ajax调用时,通常的代码格式如下:

1
2
3
4
5
6
$.ajax({
url:"pro",
data:$("#userForm").serializeArray(),
success:successFunction,
error:errorFunction
});

在该Ajax调用中通过successerror分别指定了调用成功、调用失败时的回调函数,这是jQuery传统的管理回调函数的方式,这种方式虽然简单、直观,但编程一致性并不好,因为调用不同耗时操作时可能需要通过不同选项来指定回调函数。
Deferred 对象则改变了这种局面,Deferred 对象允许用”一致”的代码来管理回调函数。例如通过done()方法添加回调成功的回调函数,通过fail()方法添加调用失败的回调函数……从jQuery 1.5开始,$.ajax()方法返回的就是Deferred对象,因此jQuery允许我们采用如下形式来完成Ajax调用。

下面的示例使用Deferred对象改写了前面的Ajax示例,程序如下。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE html>
<html>

<head>
<meta name="author" content="Yeeku.H.Lee(CrazyIt.org)" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>使用jQuery的get方法</title>
</head>

<body>
<h3>请输入你的信息:</h3>
<form id="userForm">
用户名: <input type="text" name="user" /> <br /> 喜欢的图书: <select
multiple="multiple" name="books">
<option value="java">疯狂Java讲义</option>
<option value="javaee">轻量级Java EE企业应用实战</option>
<option value="ajax">疯狂前端开发讲义</option>
<option value="xml">疯狂XML讲义</option>
</select> <br /> <input id="load" type="button" value="发送异步GET请求" />
</form>
<hr>
<div id="show"></div>
<script src="jquery-3.1.1.js" type="text/javascript">

</script>
<script type="text/javascript">
// 为id为load的按钮绑定事件处理函数
$("#load").click(function() {
// `$.ajax()`方法返回的就是`Deferred`对象
$.ajax({
url : "pro",
data : $("#userForm").serializeArray()
})
// 使用`Deferred`对象的done()方法添加“执行成功”的回调函数
.done(function(data, statusText) {
$("#show").empty();
$("#show").append("服务器响应状态为:" + statusText + "<br />");
$("#show").append(data);
})
// 使用`Deferred`对象的fail方法添加“执行失败”的回调函数
.fail(function() {
alert("服务器响应出错!");
});
});
</script>
</body>
</html>

该程序调用了$.ajax()方法来发送异步请求,该方法将会返回一个Deferred 对象,接下来通过Deferred对象的done()方法和fail()方法分别添加调用成功、调用失败的回调函数。
提示:通过上面的示例不难看出jQueryDeferred的设计:

Deferred其实就是JavaScript新增的Promise对象。它们都可对所有”耗时操作”的回调函数进行统一管理,就像jQuery最初提供的jQuery对象一样。**jQuery对象对所有DOM元素进行了统一管理,从而提供了一致的编程模型。而Deferred对象或Promise对象则可对所有耗时操作进行统一管理**。因此jQuery完全可能对原有的Ajax进行全新设计,从传统编程模型全面过渡到使用Deferred来管理回调函数。

Deferred详解

如何创建Deferred对象

除了可以调用$.ajax()方法返回Deferred对象之外,jQuery专门提供了**jQuery.Deferred()方法来创建Deferred对象**。

Deferred提供的3个耗时状态

Deferred对象提供了以下三种状态来表示耗时操作的执行结果:

  1. pending状态:pending表示任务执行中,未完成。此时将执行Deferred对象progress()方法添加的回调函数。
  2. fulfilled状态:fulfilled表示任务完成了,该任务会执行resolve函数;进而执行Deferred对象done()方法添加的回调函数。
  3. rejected状态:rejected表示任务失败了,该任务会执行reject函数;进而执行fail()方法添加的回调函数。

修改Deferred状态的方法

为了让开发者手动改变Deferred对象的状态,Deferred还提供了reject(args)resolve(args)两个方法,其中

  • reject(args)方法用于将Deferred对象改为”任务失败“状态,
  • resolve(args)方法用于将Deferred对象改为”任务完成“状态。

Deferred对象的方法详解

Deferred对象支持的方法如下。

监听操作的方法

方法 描述
progress(progressCallbacks) 指定在Deferred对象包含的异步操作执行过程中激发的回调函数。
done(doneCallbacks) 指定Deferred对象包含的异步操作执行成功后激发的回调函数。
fail(doneCallbacks) 指定Deferred对象包含的异步操作执行失败后激发的回调函数。
always(alwaysCallbacks [,alwaysCallbacks]) 该方法相当于done()fail()两个方法的合体。也就是说,无论Deferred对象包含的异步操作执行成功还是执行失败,总会激发该方法指定的回调函数。
then(doneCallbacks,failCallbacks [,progressCallbacks]) then()方法相当于done()fail()、和progress()三个方法的综合版本,该方法可以分别为Deferred对象包含的异步操作在完成时失败时进行中指定一个或多个回调函数。

调用监听操作方法的方法

方法 描述
notify(args) 调用在Deferred对象上通过progress()方法添加的函数,args作为参数传入progress()方法添加的函数。
notifyWith(context [,args]) 类似于notify(args)方法的功能,只是激发progress()方法添加的函数时将传入contextargs参数。
reject(args) Deferred对象状态改为”rejected(任务已失败)”,在Deferred 对象上通过fail()方法添加的函数将会被激发,args作为参数传入fail()方法添加的函数。
rejectWith(context [,args]) 类似于reject(args)方法的功能,只是激发fail()方法添加的函数时将传入contextargs参数。
resolve(args) Deferred对象状态改为”rejected(任务已完成)”,在Deferred对象上通过done()方法添加的函数将会被激发,args作为参数传入done()方法添加的函数。
resolveWith(context [,args]) 类似于resolve(args)方法的功能,只是激发done()方法添加的函数时将传入contextargs参数。

返回Promise对象的方法

方法 描述
promise([target]) 返回Deferred对象对应的Promise对象(它相当于Deferred对象的副本,不允许开发者通过Promise对象修改Deferred对象的状态)。如果指定了target参数,则会在该参数指定的对象上增加Deferred接口。

查询一步操作所处的执行状态

方法 描述
state() 返回Deferred对象包含的异步操作所处的执行状态。
该方法返回表示执行状态的字符串,可能返回如下三个字符串。
  1. pending:任务执行中,未完成。
  2. resolved:任务完成。
  3. rejected:任务失败。

程序示例

下面的程序定义了一个耗时操作。程序需要统计出指定范围的所有质数,由于这个过程耗时较长,因此考虑使用Deferred对象来管理回调函数。JavaScript代码如下。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html>

<head>
<meta name="author" content="Yeeku.H.Lee(CrazyIt.org)" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title> Deferred对象 </title>
</head>

<body>
<script type="text/javascript" src="../jquery-3.1.1.js">
</script>
<script type="text/javascript">
var calPrime = function (start, end) {
// 定义一个Deferred对象
var dfd = $.Deferred(); // ①
try {
var result = "";
search:
for (var n = start; n <= end; n++) {

for (var i = 2; i <= Math.sqrt(n); i++) {
// 如果除以n的余数为0,开始判断下一个数字。
if (n % i == 0) {
continue search;
}
}
// 搜集找到的质数
result += (n + ",");
}
// 当整个“耗时任务”执行完成时,将Deferred对象的状态改为resolved
dfd.resolve(result); // ②
}
catch (e) {
// 如果程序出现异常,将Deferred对象的状态改为rejected
dfd.reject("任务失败"); // ③
}
return dfd.promise();
}
// 调用calPrime()耗时函数
calPrime(1, 1000000)
// 通过done()方法添加回调函数
.done(function (result) {
$("body").append(result);
})
// 通过fail()方法添加回调函数
.fail(function (result) {
$("body").append("计算出错了!");
});
</script>
</body>

</html>

该程序中①号代码创建了一个Deferred 对象,该对象用于标识该耗时任务的完成进度:

  • 当任务完成时,程序调用Deferred对象的resolve()方法将它的状态设为resolved;
  • 当任务出错时,程序调用Deferred对象的reject()方法将它的状态设为rejected

calPrime()方法返回Deferred对象的Promise对象,

Promise对象相当于Deferred对象的副本,但程序不能通过Promise对象来改变Deferred对象的状态—通过这种方式,即可避免别人在方法外改变Deferred对象的状态。

Deferred对象用于表示calPrime()耗时操作的完成状态,因此通常只应该在该方法内改变Deferred对象的状态。为了保证效果,建议将Deferred对象定义成该方法的局部变量,并让该方法返回Deferred对象的Promise对象。
在浏览器中执行该JavaScript代码,即可看到当calPrime()耗时操作执行完成后,会自动回调done()方法指定的回调函数。