约战Angular中Promise(2)

上回说到回调金字塔会形成一个then()的调用链,以及相应在pending的等待队列产生变化,现在我们就来详细分析。

我们首先来看看我们在promise中定义的回调函数是怎样被执行的,先看看call stack:

这里写图片描述

看到最底层的三个函数我们应该觉得非常熟悉,这不就是ng的事件轮询么(详情见本人的ng事件轮询文章),从apply进入ng的事件轮询控制域,然后再digest中轮询AsyncQueue(异步事件队列)跟Watchers(在dom节点以及数据模型上注册的监听器列表),回调事件触发后就会调用$eval()函数,这个函数内部会将表达式通过parse()函数编译成一个function对象,然后进行执行,这个function对象是什么呢:

看看代码:

 nextTick(function() {
          result.resolve(callback(value));
        });

就是nextTick()函数的这个回调参数,nextTick又是qFactory()函数的一个参数,再看QProvider()(qFactory在这里被调用来构造q服务),

  this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
    return qFactory(function(callback) {
      $rootScope.$evalAsync(callback);
    }, $exceptionHandler);
  }];

这下我们应该知道nextTick()干了什么吧,就是将它的回调参数放到异步计算队列,看看$evalAsync()的源代码:

     $evalAsync: function(expr) {
        // if we are outside of an $digest loop and this is the first time we are scheduling async
        // task also schedule async auto-flush
        if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
          $browser.defer(function() {
            if ($rootScope.$$asyncQueue.length) {
              $rootScope.$digest();
            }
          });
        }
        this.$$asyncQueue.push({scope: this, expression: expr});
      }

在这段代码中,如果$rootScope中的异步事件处理队列为空的话,就defer()一个future task,就是说以后一定要给我处理异步队列的任务哦,因为通过deferred就可以获得它的promise了,然后就是干做点有意义的事情了,在事件循环的异步队列中将执行表达式push进去(这个表达式待事件执行时会调用eval函数进行parse解析,这里我们 nextTick()中的定义的匿名函数就是expr,只不过已经是一个function对象),所以nextTick()就这样把我们的异步请求提交到rootScope的事件循环中,这样我们就知道了这个回调参数就是从nextTick()中被push到事件循环中的,通过eval等函数再上图的call stack上得到调用,继续看callstack,上面我们看到调用的这个callback(value)是经过then()封装成一个wrappedCallback对象了,就是callback函数调用的结果还要经过resolve函数的处理,再来看看resolve代码:

 resolve: function(val) {
        if (pending) {
          var callbacks = pending;
          pending = undefined;
          value = ref(val);

          if (callbacks.length) {
            nextTick(function() {
              var callback;
              for (var i = 0, ii = callbacks.length; i < ii; i++) {
                callback = callbacks[i];
                value.then(callback[0], callback[1], callback[2]);
              }
            });
          }
        }
      }

这个函数在ref()函数中被调用,this为result,这是一个新通过defer()构造出来的一个对象,所以pending会被初始化,然而nextTick()又是在ref()函数中的构造的reffed(我暂且这么称呼)对象中的then()函数的回调参数,回顾上面的代码,不难看出这一切的缘来都是resolve()函数中的value,value就是上一次then()链(then触发的回调任务执行的返回值)上一次调用的返回值,在每一次then()中的任务被执行时,then()执行链就删除一个节点,pending队列就为空,同时ref产生了一个新的有then()方法的一个任务,即开始执行执行链的下一个节点,同时,我们业务逻辑层会调用一次then()方法,就这样三个封装好的异步任务就进入到pending队列中去了,下一节点怎么执行呢,看到先把当前执行链的所有等待任务都从队列中取出来给到callbacks变量,然后进行遍历,把所有的任务都添加到value的then()方法里面,添加到then()就以为着这些任务都去到nextTick的callback参数中,进入ng的事件轮询中,以后就时机到来就可以进行回调了。然后被回调后又回到我们刚刚讨论的起点,形成一个闭环了。

当然如果当前回调后的结果不是promise,在defer()出一个新的对象是会初始化pending的,但由于在业务层无需定义then()的未来任务,所以callback是空的,resolve()函数就基本没干什么实事了。好了这个分析就可以说是有始有终了,说到我们的wrappedCallback被调用其实就是我们自己定义的callback被调用了,看下callstack显而易见,至于其他的一些判断分支不是问题的关键我就不多说了。

最后说说notify这个在ng章比较有特色的一个新接口,如果要写一个文件上传啊,或者从服务器获取数据之类的耗时异步请求,就可以进行进度的监控,只需要在业务层调用notify()函数,把我们要更新的值传到里面,并在then()定义好我们的更新处理函数,然后调用notify函数后就会把通知pending里面的所有等待任务对参数进行处理,完成数据的刷新。

还是看看notify的代码吧,其实就是我上面说的意思:

notify: function(progress) {
        if (pending) {
          var callbacks = pending;

          if (pending.length) {
            nextTick(function() {
              var callback;
              for (var i = 0, ii = callbacks.length; i < ii; i++) {
                callback = callbacks[i];
                callback[2](progress);
              }
            });
          }
        }
      }

好啦,本次的分析就到这里咯,期待以后的前端框架架构的分析文章更新吧。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值