首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

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

浅谈如何降低 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 的实现方式。
返回列表