约战Angular中Promise(2)

来源:互联网 发布:淘宝客服结束语大全 编辑:程序博客网 时间:2024/04/27 10:42

上回说到回调金字塔会形成一个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
原创粉丝点击