Javascript异步流程控制之Promise(2)-Angular $q源码解读

Javascript异步流程控制之Promise(2)-Angular $q源码解读

原文写于 2015-01-27 https://github.com/kuitos/kuitos.github.io/issues/16

接上一篇 Angular qAngular q的代码实现来学习下Promise的实现原理及思想
上篇讲到了Angular Promise的基本API,其中就典型的应用类似这样

Promise.then

var defer = $q.defer();

setTimeout(function (){
    defer.resolve(10);
},5000);
console.log("1");
defer.promise.then(function(a){
    console.log(a);
});
console.log("2");

// 打印的顺序是1,2,10
// 当我们依赖的数据会在一个异步的时间点返回时,我们需要使用resolve 跟 then 来配合

首先我们要明确一个概念就是js是单线程语言,一个时间只能有一个线程在执行。那么上面的执行流程自然就是: 启动定时器并往事件队列里加入一个回调函数 —> 打印1 —> 调用defer.promise.then方法 —> 打印2 —>主线程空闲开始等5秒(实际会小于5s)执行回调 —> 调用defer.resolve —> 打印10
至于这中间究竟发生了上面,先来看看angular是怎样实现这两个方法的

  1. Promise.then

    “`js
    // Promise.then
    function Promise() {
    this.$$state = { status: 0 };
    }

    Promise.prototype = {
    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;
    },
    

    ………

“`
Promise构造函数原型上维护一个方法 then ,当一个promise对象调用该方法时会往自己的成员属性

statependingdeferredpendingdefer.promise.thendefer.promise.
state.pending = [[new Deferred(), function (a){console.log(a)}]];
再来看看defer.resolve发生了什么

  1. Deferred.resolve

    // Deferred.resolve
    Deferred.prototype = {
        resolve: function(val) {
            if (this.promise.$$state.status) return;
        		if (val === this.promise) {
          			this.$$reject($qMinErr(
            			'qcycle',
            		"Expected promise to be resolved with value other than itself '{0}'",
            		val));
        		}
        		else {
          			this.$$resolve(val);
            }
    
        },
    
        $$resolve: function(val) {
            var then, fns;
    
            fns = callOnce(this, this.$$resolve, this.$$reject);
            try {
                if ((isObject(val) || isFunction(val))) then = val && val.then;
                if (isFunction(then)) {
                    this.promise.$$state.status = -1;
            			then.call(val, fns[0], fns[1], this.notify);
          			} else {
            			this.promise.$$state.value = val;
            			this.promise.$$state.status = 1;
            			scheduleProcessQueue(this.promise.$$state);
          			}
        		} catch (e) {
          			fns[1](e);
          			exceptionHandler(e);
        		}
      		},
    ......
    ```
    	核心方法是 Deferred.$$resolve , 上文中 defer.resolve(10) 之后会走到  scheduleProcessQueue(this.promise.$$state);   
    
    ```js
    // scheduleProcessQueue
    function scheduleProcessQueue(state) {
      if (state.processScheduled || !state.pending) return;
      state.processScheduled = true;
      nextTick(function() { processQueue(state); });
    }

    nextTick方法定义在这里

    // nextTick
    function qFactory(nextTick, exceptionHandler) {
      var $qMinErr = minErr('$q', TypeError);
      .....

    再往上看谁调用了qFactory

    // qFactory
    function $$QProvider() {
      this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
        return qFactory(function(callback) {
          $browser.defer(callback);
        }, $exceptionHandler);
      }];
    }

    没错就是我们使用的 q q在初始化的时候会 生成 nextTick = function(callback) {$browser.defer(callback);}
    $browser.defer干了什么事?

    // $browser.defer
    self.defer = function(fn, delay) {
      var timeoutId;
      outstandingRequestCount++;
      timeoutId = setTimeout(function() {
        delete pendingDeferIds[timeoutId];
        completeOutstandingRequest(fn);
      }, delay || 0);
      pendingDeferIds[timeoutId] = true;
      return timeoutId;
    };

    没错,很简单,就是一个定时器。将定时器时间默认设置为0的意图很明显,使当前任务脱离主线程,在事件队列执行时再做处理,也就是所谓的defer延时
    回到 scheduleProcessQueue,被手动从主线程中移除的任务是 nextTick(function() { processQueue(state); });
    看看 processQueue 是干嘛的

    // processQueue
    function processQueue(state) {
      var fn, promise, pending;
    
      pending = state.pending;
      state.processScheduled = false;
      state.pending = undefined;
      for (var i = 0, ii = pending.length; i < ii; ++i) {
        promise = pending[i][0];
        fn = pending[i][state.status];
        try {
          if (isFunction(fn)) {
            promise.resolve(fn(state.value));
          } else if (state.status === 1) {
            promise.resolve(state.value);
          } else {
            promise.reject(state.value);
          }
        } catch (e) {
          promise.reject(e);
          exceptionHandler(e);
        }
      }
    }

    没错,这个就是Promise处理最核心的部位。当主线程执行完毕开始处理事件队列时,processQueue 开始执行, 它会将 state.pending 队列(没错,队列里的内容就是我们调用then()方法时一个个压入的)遍历然后依次调用
    假如有这样一段代码
    promiseO.then(funcA).then(funcB);
    那么执行后会发生这样一个变化

    promiseO.then(funcA) ---> promiseO.$$state.pengding = [[deferredA, funcA]]; return deferredA.promise;  
    	deferredA.promise.then(funcB) ---> deferredA.promise.$$state.pending = [[deferredB, funcB]]; return deferredB.promise;  
    
    // 当各个promise被依次resolve了
    // 1. 首先是promiseO.resolve(10) ---> promiseO.$$state.value = 10;promiseO.$$state.status = 1;scheduleProcessQueue(promiseO.$$state);
    	// 2. scheduleProcessQueue最后走到processQueue方法,会执行promiseO.$$state.pengding队列,这时候会执行 deferredA.resolve(funcA(promiseO.$$state.value))
    	// 3. 假设funcA(promiseO.$$state.value)返回20,那么就是 deferredA.resolve(20)。这时候就又回到了步骤1
    // 依次执行后then链上所有方法均会执行

    then的核心思想是它会往当前promise的pending队列中压入then链下一个promise对象的Deferred(promise=Deferred.promise),然后通过这种节点关系构成整个then链

    介绍完promise.then我们再来介绍一下$q.all([promises]).then(funcT),来看看他是怎么实现当所有promise被resolve之后再走向下一步的吧

  2. $q.all

    // $q.all
    function all(promises) {
      var deferred = new Deferred(),
          counter = 0,
          results = isArray(promises) ? [] : {};
    
      forEach(promises, function(promise, key) {
        counter++;
        when(promise).then(function(value) {
          if (results.hasOwnProperty(key)) return;
          results[key] = value;
          if (!(--counter)) deferred.resolve(results);
        }, function(reason) {
          if (results.hasOwnProperty(key)) return;
          deferred.reject(reason);
        });
      });
    
      if (counter === 0) {
        deferred.resolve(results);
      }
    
      return deferred.promise;
    }

    很简单,forEach promises列表之后,在每个promise的then链加一个回调,关键代码在这一行
    if (!(--counter)) deferred.resolve(results);
    每一个promise成功执行了then回调之后会–counter,如果某个promise是最后一个被resolve的则将所有的results resolve,这个时候他的then链就开始执行了

  3. $q.when

    //$q.when
    var when = function(value, callback, errback, progressBack) {
      var result = new Deferred();
      result.resolve(value);
      return result.promise.then(callback, errback, progressBack);
    };

    q.when(val/promiseLikeObj)/promiseLike q信任的promise对象并将值加入到then链中。

写在最后:

至此$q的几个基本api都基于源码做了简单介绍,个人在研究源码的过程中最大的收获就是对js的异步模型有了更深刻的理解,并对工程化应用有了感官的认识,强烈建议有兴趣的同学读读源码。Promise是一种异步编程模型,是当前被普遍认可的一种解决方案,目前业界还有一些其他的异步解决方案诸如 thunk、co 模型等,思路各有差异跟优缺点,有兴趣的同学可以去了解相信会对你理解js的异步模型有进一步认识。

PS:
下一篇会介绍一下ES6中的Promise的api用法,敬请期待

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值