文档章节

在使用AngularJS的过程中了解Promise(二)

遥借东风
 遥借东风
发布于 2015/12/05 20:13
字数 1953
阅读 15
收藏 0
点赞 0
评论 0

公司最近有个博客大赛,OP甚至亲自在team stand-up上要求大家积极投稿。对于我这种热爱写代码、记流水账、提炼式总结的人,拽文真的不是太想做,但是,好吧,这是所谓的impact中最容易做到的一种,那就去做吧。


Promise,对擅长前端的Dev应该众所周知,对于典型的后端Dev来说,基本可以总结为一句话:来自于Promise/A+,CommonJS指定规范,解决JS中的回调地狱和异步调用。

异步调用,由于在浏览器中JavaScript单线程运行,为了减小那些占用时间长的方法调用的线程等待时间(如向服务器请求资源),定义的某些方法需要在固定事件或响应后才调用执行。

而所谓回调地狱,就是指下面这种可以无限延伸、可读性差、可维护性差的代码调用。

$.get('api/xxx/a', function(a) {
  $.get('api/xxx/b', function(b) {
    $.get('api/xxx/c', function(c) {
      ...
    }, errorCallback);
  }, errorCallback);
}, errorCallback);

先来简单说一下AngularJS的Promise的使用,其实主要是两大对象deferred和promise。也可以准确来说,promise是deferred的一个属性对象。

defer - 延迟,promise - 承诺。

defer就好像遥控器,promise就是被遥控的炸弹。装配这一套装置的技术员他承诺,按下遥控上的绿色按钮时,左边的炸弹会被引爆,按下红色,右边的炸弹会灰飞烟灭。

他安装好了这一切,然后拿着遥控器这个句柄,去喝了茶去逛了个街,最主要的是他打了份报告——向上级申请执行引爆,上级经过深思熟虑通过了审批,还顺便选了个黄道吉日,于是技术员按下了遥控上的一个按钮,不出意外对应的炸弹被引爆。

从遥控炸弹安装完成,到最后被引爆,这一段跨越时间的执行就达到了延迟。

好吧,这是个蹩脚的比喻,在这里本人只是想将defer/promise这两个词具象一下,其不能完全说明Promise包含的完整机制。因为如果这是看上面这个举例,根本就是一个单纯的Command Pattern就可以实现。

这里回归正题,deferred和promise:

promise有一个最主要的方法then,用于注册三类回调函数:successCallBack/errorCallBack/notificationCallBack,然后生成一个新的nextPromise并将其返回。

deferred对象包含三个方法,resolve()/reject()/notify(),被调用时分别对应执行本实例promise注册的三个回调(见上行,按顺序一对一)。注意resolve和reject是二选一,也就是说炸弹的遥控器是一次性的,它只代表一次延时,炸掉一个就已经成为既定事实不可再改。

所以对于对于promise而言有三种状态,pending(还在延迟中)、resolve、reject。resolve\reject也同时叫fullfilled,即完成状态。

一段简单的使用代码:

var deferred = $q.defer();
deferred.promise.then(function success(data) {
  console.log(data);
},
function error(reason) {
  console.error(reason);
},
function notification(progress) {
  console.info(progress);
});

$timeout(function(){
    deferred.resolve('simple');
}, 3000);

看,基本上就是这样。至于我为什么说炸弹的例子不太恰当,“一”就是resolve是可以传递参数的,callback函数会对其进行加工。而炸弹的遥控器…这个无法再塞进去任何东西传递给炸弹。

考虑到这些promise更像是香肠加工流水线,定制好职能和顺序,塞进去一头猪,经历层层加工,最后出来的是香肠。

而“二”是最有意思的地方,promise支持链状调用,即promise.then(…).then(…)……。同时,达到的效果是,当defer.resolve()/reject()后,首先调用按状态调用then1的callback,then2中的callback始终是在then1的callback调用执行完以后才执行,即串行。

为什么觉得有意思呢?链状结构无非就是callback函数的最后一句return this不就得了么?

查看下angular1.3.15源码,事实上,并不是。(项目中用的是angularJS 1.2.26,因此本文中angular书写方式还是1.2的,但1.2源码释义性没有1.3的高,因此本人贴的源码都是1.3的,请谅解。)

如本人前面所写的“promise有一个最主要的方法then……,然后生成一个新的nextPromise并将其返回”。

 then: function(onFulfilled, onRejected, progressBack) {
    var result = new Deferred();

    this.$$state.pending = this.$$state.pending || [];
    this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
    if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);

    return result.promise;
  }

而它为何要这样做呢?它的目的其实是,当第一组炸弹引爆后的推动力可以顺利触发第二组炸弹的遥控器按钮。defer2.resolve(promise2callback())。

因此promise有连接几个then进行调用,即使生成了几个promise,每一个promise对应的defer遥控都在前一个promise的callback结束处,并且将callback的返回值作为resolve()的参数值。

完全可以进行验证,见下

var deferred = $q.defer(),
      promise = deferred.promise;
  
  promise.then(function(val){
    console.log("A " + val);
    return "A";
  });
  
  promise.then(function(val){
    console.log("B " + val);
    return "B";
  });
  
  deferred.resolve("P");

如果认为promise.then的链状调用,每次都是返回当前promise的话,上面这段代码就应该与promise.then().then()其实是同一个意思,然后很可惜,执行结果是

  • "A P"

  • "B P"

这里可以想一下为何Promise采用此种链状实现方式。实现Promise的目的是解决回调地狱和异步回调,也就是说痛点原型是这样:

asyncTask1('a', function(val1) {
    console.log('start task1 callback', val1 += 'b');

    asyncTask2(val1, function(val2) {
      console.log('start task2 callback', val2 += 'c');

      asyncTask3(val2, function(val3) {
        console.log('start task3 callback', val3 += 'd');
        console.log('end task3 callback');
      });
      console.log('end task2 callback');
    });
    console.log('end task1 callback');
  });

上面如果按照期望的链状设计想法,执行下来效果预想如下:

"start task1 callback"
"ab"
"end task1 callback"
"start task2 callback"
"abc"
"end task2 callback"
"start task3 callback"
"abcd"
"end task3 callback"

而事实上,按照方法栈后进先出的原则,其实结果是:

"start task1 callback"
"ab"
"start task2 callback"
"abc"
"start task3 callback"
"abcd"
"end task3 callback"
"end task2 callback"
"end task1 callback"

对按照方法栈的原则,这是不可违背的事实,但是JavaScript幸运的有setTimeout\angularJS有nextTick,在每一个asyncTask调用其回调函数时,仅需使用setTimeout\nextTick来调用callback即可。

这时链状执行asyncTask1(callback)->asyncTask2(callback) ->asyncTask3(callback)的顺序就可实现,回过头来再看一眼,callback1对于asyncTask2的调用到底提供了什么?提供执行后的结果作为入参 + 触发其调用而已,除此之外他们可以是各自完整的封装,他们连接成链(链状数据结构的“链”)。

因此上面那个例子,由promise调用,其实是这样的:

var asyncTask1 = function(data, millisecond) {
      var defer = $q.defer();
      $timeout(function() {
        defer.resolve(data);
      }, millisecond);
      return defer.promise;
    },
    asyncTask2 = asyncTask1,
    asyncTask3 = asyncTask1;

  asyncTask1('a', 5000).then(function(val1) {
    val1 += 'b';
    console.log('task1 callback', val1);
    return asyncTask2(val1, 3000);
  }).
  then(function(val2) {
    val2 += 'c';
    console.log('task2 callback', val2);
    return asyncTask3(val2, 1000);
  }).
  then(function(val3) {
    val3 += 'd';
    console.log('task3 callback', val3);
  });

angularJS Promise这块代码实现颇为有趣,另外1.3版本比之1.2,这块源码大量被重构,释义提升了不少。

总的来说,Promise的链状实现两点起到很大作用:

1、setTimeout/nextTick让回调跳出了先进后出的尴尬;

2、每一对Defer/Promise对象都是天生“绝”配。链状的执行,其实是前一个promise注册的回调与下一个defer的resolve/reject尾首向触发的过程。

这里也只是拿多个异步任务回调粗略的描述了一下,在项目中用的最多的then链形式,还是一个异步触发多个顺序回调处理而已,由于其从第一个then回调开始就直接return“确定内容的对象”,后续promise状态及入参判断简单,这里对于这样情况就不再描述。

© 著作权归作者所有

共有 人打赏支持
遥借东风
粉丝 1
博文 28
码字总数 21283
作品 0
武汉
高级程序员
Angular 6.0正式版发布,都有哪些新功能

点击关注异步图书,置顶公众号 每天与你分享IT好书 技术干货 职场知识 在Angular 5发布半年之后,Angular 6在昨天正式发布,那么在这个版本有哪些新功能呢?新版本重点关注工具链以及工具链在...

异步社区 ⋅ 05/08 ⋅ 0

Angular 6正式版发布,都有哪些新功能

在Angular 5发布半年之后,Angular 6在昨天正式发布,那么在这个版本有哪些新功能呢?新版本重点关注工具链以及工具链在 Angular 中的运行速度问题。除此之外,这次更新还包括框架包(@angu...

code_xzh ⋅ 05/05 ⋅ 0

【前端】—聊聊我认识的Angular

前言 最近接触的项目前端用到了Angular框架,之前略有耳闻,从vue换到Angular,感觉东西差不多,还是要系统学习的,先来了解下。 正文 1、Angular 的发展 AngularJS 是一款来自Google的前端J...

zt15732625878 ⋅ 05/19 ⋅ 0

[Angular Material完全攻略] Day 01 - 开始 & 简介

转载 从Angular第2版正式release后,根据全球最大工程师讨论区StackOverflow的统计,从2016开始的Angular讨论度就不断窜升,甚至超越了React,直到了2017年,甚至摆脱了前一代Angularjs的阴影...

readilen ⋅ 05/21 ⋅ 0

勿以 star 数论高低!React 在前端框架世界仍是领先地位

上周在前端的 JavaScript 框架世界发生了一件称得上是“里程碑”的事:Vue.js 在 GitHub 上的 star 数量终于超过了 React.js 的 star 数量。 通过使用一个叫做 Star History 的工具,我们可以...

局长 ⋅ 昨天 ⋅ 0

[Angular Material完全攻略] Day 02 - 环境设定 & 安装 & Hello World

今天我们将开始正式迈入Angular Material的世界,要学习使用Angular Material打造高品质及高质感的网页,当然要从安装Angular Material套件开始,本篇文章就来介绍基本的Angular Material安装...

readilen ⋅ 05/21 ⋅ 0

WebStorm 2018.1.3 新增与 Angular 6 支持相关的改进

WebStorm 2018.1.3 已发布,请使用 Toolbox App 或 IDE 进行更新,或直接下载 WebStorm 2018.1.3。 官方的发布公告显示,本次更新添加了许多与 Angular 6 支持相关的改进(包括从 IDE 欢迎屏...

局长 ⋅ 05/10 ⋅ 0

JavaScript MVW 框架 - AngularJS

Angular JS (Angular.JS) 是一组用来开发 Web 页面的框架、模板以及数据绑定和丰富 UI 组件。它支持整个开发进程,提供 Web 应用的架构,无需进行手工 DOM 操作。 AngularJS 很小,只有 60K,...

匿名 ⋅ 2011/01/20 ⋅ 44

初学angular 看到网上有angular js 也有angular2 ,到angular官网发现最近版本是6了,那么现在大家说的angular js到底是什么啊?

初学angular 看到网上有angular js 也有angular2 ,到angular官网发现最近版本是6了,那么现在大家说的angular js到底是什么啊? angular2和现在官网的angular6 就是 angular js 只是版本不同...

Jordan裔 ⋅ 05/19 ⋅ 0

AngularJS 的 Material Design 风格框架 - AngularJS Material

AngularJS Material 是 AngularJS 框架的谷歌 Material Design 标准的实现。AngularJS Material 包含一组丰富的、可重用、经过充分测试并可访问的 UI 组件。 针对 Angular 2 或更高版本的实现...

匿名 ⋅ 05/15 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

从 Confluence 5.3 及其早期版本中恢复空间

如果你需要从 Confluence 5.3 及其早期版本中的导出文件恢复到晚于 Confluence 5.3 的 Confluence 中的话。你可以使用临时的 Confluence 空间安装,然后将这个 Confluence 安装实例升级到你现...

honeymose ⋅ 今天 ⋅ 0

Java8新增的DateTimeFormatter与SimpleDateFormat的区别

两者最大的区别是,Java8的DateTimeFormatter也是线程安全的,而SimpleDateFormat并不是线程安全。 在并发环境下使用SimpleDateFormat 为了能够在多线程环境下使用SimpleDateFormat,有这三种...

人觉非常君 ⋅ 今天 ⋅ 0

多线程如何控制执行顺序

线程的生命周期说明: 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、...

MarinJ_Shao ⋅ 今天 ⋅ 0

用ZBLOG2.3博客写读书笔记网站能创造今日头条的辉煌吗?

最近两年,著名的自媒体网站今日头条可以说是火得一塌糊涂,虽然从目前来看也遇到了一点瓶颈,毕竟发展到了一定的规模,继续增长就更加难了,但如今的今日头条规模和流量已经非常大了。 我们...

原创小博客 ⋅ 今天 ⋅ 0

MyBatis四大核心概念

本文讲解 MyBatis 四大核心概念(SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper)。 MyBatis 作为互联网数据库映射工具界的“上古神器”,训有四大“神兽”,谓之:Sql...

waylau ⋅ 今天 ⋅ 0

以太坊java开发包web3j简介

web3j(org.web3j)是Java版本的以太坊JSON RPC接口协议封装实现,如果需要将你的Java应用或安卓应用接入以太坊,或者希望用java开发一个钱包应用,那么用web3j就对了。 web3j的功能相当完整...

汇智网教程 ⋅ 今天 ⋅ 0

2个线程交替打印100以内的数字

重点提示: 线程的本质上只是一个壳子,真正的逻辑其实在“竞态条件”中。 举个例子,比如本题中的打印,那么在竞态条件中,我只需要一个方法即可; 假如我的需求是2个线程,一个+1,一个-1,...

Germmy ⋅ 今天 ⋅ 0

Django第一期

安装Django 去https://www.djangoproject.com/download/ 下载最新版的Django,然后解压放到Anaconda\Lib\site-packages目录下,然后cmd进入此目录,输入安装命令: python setup.py install ...

大不了敲一辈子代码 ⋅ 今天 ⋅ 0

Springboot2 之 Spring Data Redis 实现消息队列——发布/订阅模式

一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式,这里利用redis消息“发布/订阅”来简单实现订阅者模式。 实现之前先过过 redis 发布订阅的一些基础概念和操...

Simonton ⋅ 今天 ⋅ 0

error:Could not find gradle

一.更新Android Studio后打开Project,报如下错误: Error: Could not find com.android.tools.build:gradle:2.2.1. Searched in the following locations: file:/D:/software/android/andro......

Yao--靠自己 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部