浅谈如何降低 HTML5 的异步编程复杂度(1)准备工作
 
- UID
- 1066743
|

浅谈如何降低 HTML5 的异步编程复杂度(1)准备工作
准备工作假设一模拟场景,需求如下:
- 从服务端获取员工列表数据(基本数据:员工 ID,姓名,年龄),并存于客户端;
- 对于年龄在 30 岁以上的员工,获取其详细数据(基本数据及其入职时间),并存于客户端。
您可以根据自身经验,先评估用传统回调方式完成该需求的时间,然后和下文中的具体实现做比较。附件则为本文例子相关源码,使用技术主要有 SpringMVC + jQuery/Wind.js/JSDeferred + Web SQL(HTML5)等,基于 Maven 构建。
如果您对 HTML5 的 Web SQL 不是很熟悉,可以先阅读本站 HTML5 专题中的相关文章。
JavaScript 异步编程对 JavaScript 异步编程概念不熟悉的读者可先阅读阮一峰的 。引用他的话来说,JavaScript 执行环境是“单线程”。而所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。那么这种语言特性导致的结果就是:JavaScript 代码风格大多以回调写法为主。HTML4 规范下,您也许写过以下场景的代码:
- 通过 Ajax 获取服务端数据;
- 使用某个 UI 框架的 Dialog,定义某个按钮点击时的回调函数;
- 实现某个动画。
等等,那么步入 HTML5 时代,您可能还会基于回调方式写一下场景的代码:
- 实现离线存储功能;
- 实现文件读写功能;
- 基于 Worklight、PhoneGap 等框架提供的 JavaScript API 来使用移动设备资源。
日益种类繁多的回调式写法势必会给开发带来更多复杂度,下面将简单介绍几种改善异步编程体验的方式:
Promise 方式Promise 规范Promise 规范由 组织提出,目前还处于草案阶段。就设计目的简单的说,就是提供一个对象,通过该对象的方法,来代表某个异步操作的成功,失败等;现有草案(包含校订中的)主要有 5 个: 、 、 、Promises/C(校订中)、 。即便该规范还不是标准,jQuery 1.5+、Dojo 1.7+、Node.js 中的 node-promise 等已经实现了 Promises/A 的规范,有些还提供了扩展功能,如中断操作,多个异步操作同时执行等;而 Node.js 则实现了 Promises/B 和 Promises/D。下面将主要介绍 Promises/A 以及 jQuery 的实现方式。
Promises/A 规范主要有以下几点:
- 创建一个 promise 对象代表异步操作;
- 该对象有三种状态:未完成(unfulfilled)、完成(fulfilled)、失败(failed);
- 该对象提供一个名为 then 的方法,该方法有三个入参,依次能传入完成时的函数句柄(fulfilledHandler),失败时的函数句柄(errorHandler)以及获取当前进展的函数句柄(progressHandler);
- then 方法会返回一个新的 promise 对象,这为链式(chain)操作提供便利。
jQuery 的实现jQuery 从 1.5 起引入了 类,实现了 Promises/A 规范,并提供一些增强功能,下面是一个简单的例子:如果获得一个偶数,异步操作成功,反之失败。希望通过这个例子能让读者对 Promise 规范有一个直观了解:
清单 1.jQuery 的 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
| $(document).ready(function() {
var asyncTask = function(idx) {
var dfd = $.Deferred(); // 获得一个 Deferred 实例
setTimeout(function() {
var result = parseInt(Math.random() * 100, 10) % 2,
time = (new Date()).getTime(),
prefix = "jQuery " + time + " " + idx + ":";
if (result == 0) {
dfd.resolve(prefix + "got an even number"); // 如果得到偶数,表示操作成功
} else {
dfd.reject(prefix + "failed to get an even number"); // 反之表示操作失败
}
}, 2000); // 延迟2秒执行函数
return dfd; // 返回Deferred实例
}
function printResult (result) {
console.info(result);
}
for (var i=0; i<5; i++) {
asyncTask(i).then(printResult, printResult); // 执行5次,输出成功或者失败结果
}
});
|
图 1.在 Chrome 运行的结果 需求实现回到模拟场景,我们将创建一个 Employee 类,然后基于 Promise 规范创建三个关键方法:
- 从服务端获取员工列表数据,代码见清单 2;
- 从服务端获取详细数据;
- 在客户端保存员工数据。
然后按场景需求,将这三个方法组合使用,代码见清单 3。
清单 2.从服务端获取员工列表数据的代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| Employee.all = function(opt) {
var opt = opt || {local: true}, dfd = $.Deferred();
if (opt.local === true) {
throw new Error('not implemented!'); // 暂不实现离线存储部分的代码
} else {
// 通过ajax获取员工数据, jQuery的ajax方法也同样返回了一个Deferred实例
$.ajax({
url: "/async/api/employee",
dataType: "json"
}).then(function(items) {
var employees = [];
$(items).each(function(idx, item) {
employees.push(new Employee(item))
});
dfd.resolve(employees);
}, function(err) {
dfd.reject(err);
});
}
return dfd;
};
|
清单 3.实现模拟场景的代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| $(document).ready(function() {
var msg;
Employee.all({local: false}).then(function(employees) { // 从服务器读取员工数据
$(employees).each(function(idx, employee) {
if (employee.age >= 30) {
Employee.get(employee.id, {local: false}). // 从服务器读取员工明细
then(function(employee) {
employee.create({local: true}); // 将年龄大于30(含)的员工明细存于本地
}, function(err) {
msg = 'failed to retrieve the detail info from the server';
console.error(msg);
});
} else {
employee.create({local: true}); // 将年龄小于30的员工明细存于本地
}
});
}, function(err) {
msg = 'failed to retrieve all employee\'s info from the server.';
console.error(msg);
});
});
|
图 2.清单 3 代码的运行结果 由此可见,代码的可读性和可维护性得到了一定的提高,细心的读者也许会发现,for 循环中代码并不是顺序执行的。针对这个缺陷,我们会介绍两种非 Promise 的实现方式。 |
|
|
|
|
|