《AngularJS深度剖析与最佳实践》一2.10 承诺

简介:

本节书摘来自华章出版社《AngularJS深度剖析与最佳实践》一书中的第2章,第2.10节,作者 雪狼 破狼 彭洪伟,更多章节内容可以访问云栖社区“华章计算机”公众号查看

2.10 承诺

承诺(Promise)不是Angular首创的。作为一种编程模式,它出现在……1976年,比JavaScript还要古老得多。Promise全称是 Futures and promises(未来与承诺)。要想深入了解,可以参见http://en.wikipedia.org/wiki/Futures_and_promises
而在JavaScript世界中,一个广泛流行的库叫作Q(https://github.com/kriskowal/q)。而Angular中的$q就是从它引入的。
1.生活中的一个例子
Promise解决的是异步编程的问题,对于生活在同步编程世界中的程序员来说,它可能比较难于理解,这也构成了Angular入门门槛之一,本节将用生活中的一个例子对此做一个形象的讲解。
假设有一个家具厂,而它有一个VIP客户张先生。
有一天张先生需要一个豪华衣柜,于是,他打电话给家具厂说:“我需要一个衣柜,回头做好了给我送来”,这个操作就叫$q.defer(),也就是延期。因为这个衣柜不是现在要的,所以张先生这是在发起一个可延期的请求。
家具厂接下了这个订单,给他留下了一个回执号,并对他说:“我们做好了会给您送过去,放心吧”。这叫作Promise,也就是给了张先生一个“承诺”。
这样,这个defer算是正式创建了,于是他把这件事记录在自己的日记上,并且同时记录了回执号,这个变量叫作deferred,也就是已延期事件。
现在,张先生就不用再去想着这件事了,该做什么做什么,这就是“异步”请求的含义。
假设家具厂在一周后做完了这个衣柜,并如约送到了张先生家(包邮哦,亲),这就叫作deferred.resolve(衣柜),也就是“问题已解决,这是您的衣柜”。而这时候张先生只要取出一下这个“衣柜”参数就行了。而且,这个“邮包”中也不一定只有衣柜,还可以包含别的东西,比如厂家宣传资料、产品名录等。整个过程中轻松愉快,谁也没等谁,没有浪费任何时间。
假设家具厂在评估后发现这个规格的衣柜我们做不了,那么它就需要deferred.reject(理由),也就是“我们不得不拒绝您的请求,因为……”。拒绝没有时间限制,可以发生在给出承诺之后的任何时候,甚至可能发生在快做完的时候。而且拒绝时候的参数也不仅仅限于理由,还可以包含一个道歉信,违约金之类的。总之,你想给他什么就给他什么,如果你觉得不会惹恼客户,那么不给也没关系。
假设家具厂发现,自己正好有一个符合张先生要求的存货,它就可以用$q.when(现有衣柜)来兑现给张先生的承诺。于是,这件事立刻解决了,皆大欢喜。张先生可不在乎你是从头做的还是现有的成品,只要达到自己的品质要求就满意了。
假设这个家具厂对客户格外的细心,它还可以通过deferred.notify(进展情况)给张先生发送进展情况的“通知”。
这样,整个异步流程圆满完成!无论成功还是失败,张先生都没有往里面投入任何额外的时间成本。
好,我们再扩展一下这个故事:
张先生又来订货了,这次他分多次订了一张桌子,三把椅子,一张席梦思。但他不希望今天收到个桌子,明天收到个椅子,后天又得签收一次席梦思,而是希望家具厂做好了之后一次性送过来,但是他当初又是分别下单的,那么他就可以重新跟家具厂要一个包含上述三个承诺的新承诺,这就是$q.all([桌子承诺,椅子承诺,席梦思承诺]),这样,他就不用再关注以前的三个承诺了,直接等待这个新的承诺完成,到时候只要一次性签收了前面的这些承诺就行了。
2.回调地狱和Promise
通过上面这个生活中例子,相信作为读者的你已经了解到了异步和Promise的方式。为什么我们需要Promise呢?
JavaScript是一门很灵活的语言,由于它寄宿在浏览器中以事件机制为核心,所以在我们的JavaScript编码中存在很多的回调函数。这是一个高性能的编程模式,所以它衍生出了基于异步I/O的高性能Nodejs平台。但是如果不注意我们的编码方法,那么我们就会陷入“回调地狱”,也有人称为“回调金字塔”。嵌套式的回调地狱,代码将会变得像意大利面条一样。如下边的嵌套回调函数一样:

async1(function(){
    async2(function(){
        async3(function(){
            async4(function(){
                ....
            });
        });
    });
});

这样嵌套的回调函数,让我们的代码的可读性变得很差,而且很难于调试和维护。所以为了降低异步编程的复杂性,开发人员一直寻找简便的方法来处理异步操作。其中一种处理模式称为Promise,它代表了一种可能会长时间运行而且不一定必须完成的操作的结果。这种模式不会阻塞和等待长时间的操作完成,而是返回一个代表了承诺的(Promised)结果的对象。它通常会实现一种名叫then的方法,用来注册状态变化时对应的回调函数。
Promise在任何时刻都处于以下三种状态之一:未完成(pending)、已完成(resolved)和拒绝(rejected)三个状态。以CommonJS Promise/A 标准为例,Promise对象上的then方法负责添加针对已完成和拒绝状态下的处理函数。then方法会返回另一个Promise对象,以便于形成Promise管道,这种返回Promise对象的方式能够让开发人员把异步操作串联起来,如then(resolvedHandler, rejectedHandler)。resolvedHandler回调函数在Promise对象进入完成状态时会触发,并传递结果;rejectedHandler函数会在拒绝状态下调用。
所以我们上边的嵌套回调函数可以修改为:
async1().then(async2).then(async3).catch(showError);
这下代码看着清爽多了,我们不再需要忍受嵌套的无底深渊。
在ES6的标准版中已经包含了Promise的标准,很快它就将会从浏览器本身得到更好的支持。与此同时在ES6的标准版中,还引入了Python这类语言中的generator(迭代器的生成器)概念,它本意并不是为异步而生的,但是它拥有天然的yield暂停函数执行的能力,并保存上下文,再次调用时恢复当时的状态,所以它也被很好地运用于JavaScript的异步编程模型中,其中最出名的案例当属Node Express的下一代框架KOA了。
最后还有个好消息,在ES7的标准中将有可能引入async和await这两个关键词,来更大的简化我们的JavaScript异步编程模型。我们就可以如下的方式以同步的方式编写我们的异步代码:

async function sleep(timeout) {  
    return new Promise((resolve, reject) => {
        setTimeout(function() {
            resolve();
        }, timeout);
    });
}

(async function() {
    console.log('做一些事情,' + new Date());
    await sleep(3000);
    console.log('做另一些事情,' + new Date());
})();
  1. Angular中的Promise
    在Angular中大量使用着Promise,最简单的是$timeout的实现,我拷贝过来并加上了注释:
function timeout(fn, delay, invokeApply) {
    // 创建一个延期请求
    var deferred = $q.defer(),
        promise = deferred.promise,
        skipApply = (isDefined(invokeApply) && !invokeApply),
        timeoutId;

    timeoutId = $browser.defer(function() {
        try {
            // 成功,将触发then的第一个回调函数
            deferred.resolve(fn());
        } catch(e) {
            // 失败,将触发then的第二个回调函数或catch的回调函数
            deferred.reject(e);
            $exceptionHandler(e);
        } finally {
            delete deferreds[promise.
$$
timeoutId];
        }

        if (!skipApply) $rootScope.$apply(); 
    }, delay);

    promise.
$$
timeoutId = timeoutId;
    deferreds[timeoutId] = deferred;
    // 返回承诺
    return promise;
}

timeout.cancel = function(promise) {
    if (promise && promise.
$$
timeoutId in deferreds) {
        deferreds[promise.
$$
timeoutId].reject('canceled');
        delete deferreds[promise.
$$
timeoutId];
        return $browser.defer.cancel(promise.
$$
timeoutId);
    }
    return false;
};
相关文章
|
2天前
|
搜索推荐 编译器 Linux
一个可用于企业开发及通用跨平台的Makefile文件
一款适用于企业级开发的通用跨平台Makefile,支持C/C++混合编译、多目标输出(可执行文件、静态/动态库)、Release/Debug版本管理。配置简洁,仅需修改带`MF_CONFIGURE_`前缀的变量,支持脚本化配置与子Makefile管理,具备完善日志、错误提示和跨平台兼容性,附详细文档与示例,便于学习与集成。
253 116
|
17天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
11天前
|
安全 Java Android开发
深度解析 Android 崩溃捕获原理及从崩溃到归因的闭环实践
崩溃堆栈全是 a.b.c?Native 错误查不到行号?本文详解 Android 崩溃采集全链路原理,教你如何把“天书”变“说明书”。RUM SDK 已支持一键接入。
650 220
|
存储 人工智能 监控
从代码生成到自主决策:打造一个Coding驱动的“自我编程”Agent
本文介绍了一种基于LLM的“自我编程”Agent系统,通过代码驱动实现复杂逻辑。该Agent以Python为执行引擎,结合Py4j实现Java与Python交互,支持多工具调用、记忆分层与上下文工程,具备感知、认知、表达、自我评估等能力模块,目标是打造可进化的“1.5线”智能助手。
886 61
|
9天前
|
人工智能 移动开发 自然语言处理
2025最新HTML静态网页制作工具推荐:10款免费在线生成器小白也能5分钟上手
晓猛团队精选2025年10款真正免费、无需编程的在线HTML建站工具,涵盖AI生成、拖拽编辑、设计稿转代码等多种类型,均支持浏览器直接使用、快速出图与文件导出,特别适合零基础用户快速搭建个人网站、落地页或企业官网。
1472 157
|
6天前
|
编解码 Linux 数据安全/隐私保护
教程分享免费视频压缩软件,免费视频压缩,视频压缩免费,附压缩方法及学习教程
教程分享免费视频压缩软件,免费视频压缩,视频压缩免费,附压缩方法及学习教程
282 139
|
8天前
|
存储 安全 固态存储
四款WIN PE工具,都可以实现U盘安装教程
Windows PE是基于NT内核的轻量系统,用于系统安装、分区管理及故障修复。本文推荐多款PE制作工具,支持U盘启动,兼容UEFI/Legacy模式,具备备份还原、驱动识别等功能,操作简便,适合新旧电脑维护使用。
593 109