angular源碼剖析之Provider系列--QProvider

QProvider 簡介

源碼裏是這麼描述的:html

A service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing.android

大概意思是幫助你異步執行方法,且當他們執行完後可使用他們的返回值。git

This is an implementation of promises/deferred objects inspired by Kris Kowal's Q.es6

這是一個 promises/deferred 對象的實現,靈感來自於 Kris Kowal's Qgithub

QProvider 用法

下面的例子假設$q和asyncGreet在當前做用域內是有效的.express

function asyncGreet(name) {
    // perform some asynchronous operation, resolve or reject the promise when appropriate.
    return $q(function(resolve, reject) {
      setTimeout(function() {
        if (okToGreet(name)) {
          resolve('Hello, ' + name + '!');
        } else {
          reject('Greeting ' + name + ' is not allowed.');
        }
      }, 1000);
    });
    }

    var promise = asyncGreet('Robin Hood');
    //then函數放入pending數組
    promise.then(function(greeting) {
    alert('Success: ' + greeting);
    }, function(reason) {
    alert('Failed: ' + reason);
    });

下面咱們深刻源碼內部去一探究竟:api

$QProvider 定義

function $QProvider() {

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

  $evalAsync: function(expr, locals) {
       // if we are outside of an $digest loop and this is the first time we are scheduling async
       // task also schedule async auto-flush
       //若是當前不處於$digest或者$apply的過程當中(只有在$apply和$digest方法中才會設置$$phase這個字段),而且asyncQueue數組中還不存在任務時,
       //就會異步調度一輪digest循環來確保asyncQueue數組中的表達式會被執行
       if (!$rootScope.$$phase && !asyncQueue.length) {
         $browser.defer(function() {
           //最終調用的是setTimeout
           if (asyncQueue.length) {
             $rootScope.$digest();//執行消化功能
           }
         });
       }
       asyncQueue.push({scope: this, expression: $parse(expr), locals: locals});
     }

由以前 cacheFactory的分析
,再結合上面源碼咱們就知道 注入$q時調用了qFactory工廠方法:數組

qFactory

function qFactory(nextTick, exceptionHandler) {

      function Promise() {
      //初始化Promise的狀態對象
      this.$$state = { status: 0 };
    }
    //擴展Promise類原型
    extend(Promise.prototype, {
      //then主要是把一個defer對象和fullfiled reject 函數 放入pending數組
      then: function(onFulfilled, onRejected, progressBack) {
        if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
          return this;
        }
        var result = new Deferred();

        this.$$state.pending = this.$$state.pending || [];
        //把一個新的 Defer 對象push進pending數組
        this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);

        if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);

        //返回這個新建Defer對象的promise
        //能夠造成promise chain
        return result.promise;
      }
      //代碼省略
    }
    //經過$q注入時返回Q函數
    //隨後Q函數 傳入resolver參數調用
    //調用resolver函數時傳入包裝deferred對象的resolve和 reject函數
    //隨後返回promise對象
    //promise對象調用then函數時放入pending隊列
    var $Q = function Q(resolver) {
       if (!isFunction(resolver)) {
         throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
       }
       //構造一個Deferred對象
       var deferred = new Deferred();
       function resolveFn(value) {
         deferred.resolve(value);
       }
       function rejectFn(reason) {
         deferred.reject(reason);
       }
        //調用resolver參數函數
        //resolveFn供外部調用
       resolver(resolveFn, rejectFn);
       //返回一個promise對象
       //供調用then函數
       return deferred.promise;
     };
      // Let's make the instanceof operator work for promises, so that
      // `new $q(fn) instanceof $q` would evaluate to true.
      //使得new $q(fn) 也能夠調用promise的方法
      $Q.prototype = Promise.prototype;

      //暴露內部方法
      $Q.defer = defer;
      $Q.reject = reject;
      $Q.when = when;
      $Q.resolve = resolve;
      //$q.all是用於執行多個異步任務進行回調,它能夠接受一個promise的數組,
      //或是promise的hash(object)。任何一個promise失敗,都會致使整個任務的失敗。
      //https://blog.csdn.net/shidaping/article/details/52398925
      $Q.all = all;
      //$q.race() 是 Angular 裏面的一個新方法,和 $q.all() 相似,可是它只會返回第一個處理完成的 Promise 給你///。假定 API 調用 1 和 API 調用 2 同時執行,而 API 調用 2 在 API 調用 1 以前處理完成,那麼你就只會獲得 //API 調用 2 的返回對象。換句話說,最快(處理完成)的 Promise 會贏得返回對象的機會:
      $Q.race = race;

      return $Q;
  }

調用then方法時其實是新建一個defer對象放入pending數組,在調用defer.resolve的時候
去調度這個數組中的元素,也就是任務.promise

resolve 方法

extend(Deferred.prototype, {
    resolve: function(val) {
      //第一次resolve的時候爲0 因此會往下走
      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 {
        //調度pending數組裏的任務
        this.$$resolve(val);
      }
    },

    $$resolve: function(val) {
      var then;
      var that = this;
      var done = false;
      try {
        if ((isObject(val) || isFunction(val))) then = val && val.then;
        if (isFunction(then)) {
           //val.then方法 val是promise的時候
           //resolvePromise函數裏放入了當前defer對象
          this.promise.$$state.status = -1;
          then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify));
        } else {

          //更新promise的狀態對象
          this.promise.$$state.value = val;
          this.promise.$$state.status = 1;
          //調度的時候pending爲空就返回了
          scheduleProcessQueue(this.promise.$$state);
        }
      } catch (e) {
        rejectPromise(e);
        exceptionHandler(e);
      }

      function resolvePromise(val) {
        if (done) return;
        done = true;
        that.$$resolve(val);
      }
      function rejectPromise(val) {
        if (done) return;
        done = true;
        that.$$reject(val);
      }
    }
  }

scheduleProcessQueue 方法

//傳入promise的state對象
  function scheduleProcessQueue(state) {
   if (state.processScheduled || !state.pending) return;
   state.processScheduled = true;
   //nextTick裏調用$browser.defer函數

   nextTick(function() { processQueue(state); });
  }

processQueue 方法

實際最終處理的仍是processQueue函數,裏面循環調用pending數組微信

function processQueue(state) {
    var fn, deferred, pending;

    pending = state.pending;
    state.processScheduled = false;
    state.pending = undefined;
    for (var i = 0, ii = pending.length; i < ii; ++i) {

      //獲取pending數組的元素 元素自己也是數組
      deferred = pending[i][0];
      fn = pending[i][state.status];
      try {
        if (isFunction(fn)) {
          deferred.resolve(fn(state.value));
        } else if (state.status === 1) {
          deferred.resolve(state.value);
        } else {
          deferred.reject(state.value);
        }
      } catch (e) {
        deferred.reject(e);
        exceptionHandler(e);
      }
    }
  }

歡迎關注個人微信公衆號,獲取最新源碼解析文章!

參考資料:

  1. a tiny implementation of Promises/A+.
  2. Angular中的$q的形象解釋及深刻用法
  3. 關於 Angular 裏的 $q 和 Promise
  4. 理解狀態機
相關文章
相關標籤/搜索