文档章节

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

遥借东风
 遥借东风
发布于 2015/12/05 20:13
字数 1953
阅读 25
收藏 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
博文 32
码字总数 22706
作品 0
武汉
高级程序员
Angular.js 相关记录

AngularJS作用域文档:http://docs.angularjs.org/api/ng.$rootScope.Scope ng-view 指令的角色是为当前路由把对应的视图模板载入到布局模板中。 AngularJS内置过滤器:http://code.angular...

彭博
2014/04/25
0
2
Angular中ui-grid的使用详解

Angular中ui-grid的使用   在项目开发的过程中,产品经理往往会提出各种需求,以提高用户体验。最近,项目中用到的表格特别多,而且表格的列数和行数也超多。为了让用户浏览更爽,产品经理...

半指温柔乐
08/05
0
0
AngularJS 迁移到 Angular 路径建议工具 - ngMigration Assistant

ngMigration Assistant 是一个易于使用的命令行实用程序,可扫描 AngularJS 应用程序并建议如何迁移到 Angular。 安装 globally 模式安装 ngma npm install -g ngma yarn global add ngma 运...

匿名
08/22
0
0
【前端】Angular组件钩子

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m18633778874/article/details/82259409 Angular中的钩子方法,是非常常用的知识点,也在项目代码运用中有所体...

冯浩月
08/31
0
0
实践总结 - 不可错过的Angular应用技巧

angular的核心思想是通过数据驱动一切,其他东西都是数据的延伸. 套用Javascript一切皆对象的思想,在angular中可以说一切皆数据. 关于项目构建 (1) requirejs以及Yeoman 在刚开始接触或者使用...

顽Shi
2014/07/21
0
22

没有更多内容

加载失败,请刷新页面

加载更多

通过ajax访问远程天气预报服务

http://www.webxml.com.cn/zh_cn/index.aspx 更改wsdl文件 打开文件将15行,51行,101行去掉 然后把文件复制到c盘 然后在桌面上面就生成了文件 将文件打成jar包 package cn.it.ws.weather;...

江戸川
21分钟前
1
0
聊聊storm的tickTuple

序 本文主要研究一下storm的tickTuple 实例 TickWordCountBolt public class TickWordCountBolt extends BaseBasicBolt { private static final Logger LOGGER = LoggerFactory.getLogg......

go4it
25分钟前
1
0
自动装箱和自动拆箱

自动装箱和自动拆箱 Java 提供了 8 种基本数据类型,每种数据类型都有其对应的包装类型,包装类是面向对象的类,是一种高级的数据类型,可以进行一些比较复杂的操作,它们是引用类型而不再基...

tsmyk0715
45分钟前
2
0
简易审计系统

1、有时候我们需要对线上用户的操作进行记录,可以进行追踪,出现问题追究责任,但是linux自带的history并不会实时的记录(仅仅在内存中,当用户正常退出(exit logout )时才会记录到history文件里...

芬野de博客
49分钟前
2
0
Qt那些事0.0.6

QML中使用Image,在设置source的后,通过Qt Quick2 Preview(qmlscene)遇到了图片找不到的问题: Image { id: success_img anchors.centerIn: parent ...

Ev4n
50分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部