Koa源碼分析(二) -- co的實現

Abstract

本系列是關於Koa框架的文章,目前關注版本是Koa v1。主要分爲如下幾個方面:git

  1. Koa源碼分析(一) -- generatores6

  2. Koa源碼分析(二) -- co的實現github

  3. Koa源碼分析(三) -- middleware機制的實現web

co

大名鼎鼎的co是什麼?它是TJ大神基於ES6的一些新特性開發的異步流程控制庫,基於它所開發的koa被視爲將來主流的web框架。segmentfault

koa基於co實現,而co又是使用了ES6的generator和promise特性。若是還不理解,能夠查看阮一峯老師的《ECMAScript 6 入門 --- Generator》《ECMAScript 6 入門 --- Promise》。目前co升級爲4.X版本事,代碼進行了一次很有規模的重構,咱們主要關注co(4.X)的實現思路和源碼分析。數組

使用示例

co(function* (){
    var a = yield Promise.resolve('one');
    console.log(a);
    var b = yield Promise.reslove('two');
    console.log(b);
    return 'three';
}).then((value) => console.log(value));
// one
// two
// three
co(function* (){
    var res = yield [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];
    return res;
}).then((value) => console.log(res));
// [1, 2, 3]

根據co的功能,它做爲異步流程控制的做用,自動調用generator對象的next()方法,實現generator函數的運行,並返回最終運行的結果。promise

若是要涉及到co的實現細節,咱們就會存在如下幾個疑問:app

  1. 如何依次調用next()方法框架

  2. 如何將yield後邊運算異步結果返回給對應的變量koa

  3. co自身如何返回generator函數最後的return值

接下來咱們正對以上問題,分析TJ大神的源碼

源碼解析

co源碼的流程控制

function co(gen) {
    // 保持當前函數的上下文
    var ctx = this;
    // 截取co輸入的參數,剔除arguments中的第一個參數,即gen對象,剩餘參數做爲gen的入參
    var args = slice.call(arguments, 1);
    
    // 返回一個Promise對象,即最外圍Promise對象
    return new Promise(function(resolve, reject) {
        // 判斷傳入的gen是否爲函數,如果則執行,將結果賦值給gen對象
        // 若不是,則不執行
        if (typeof gen === 'function') gen = gen.apply(ctx, args);
        // 根據generator函數執行結果是否存在next字段,判斷gen是否爲generator迭代器對象
        // 若不是,則調用resolve返回最外圍Promise對象的狀態
        if (!gen || typeof gen.next !== 'function') return resolve(gen);
        
        // 如果generator迭代器對象,開始控制gen.next()方法的調用
        onFulfilled();

        // 兩個用途
        // 1、generator函數的執行入口
        // 2、當作全部內部Promise對象的resolve方法,處理異步結果,並繼續調用下一個Promise
        function onFulfilled(res) {
            var ret;
            try {
                // gen運行至yield處被掛起,開始處理異步操做,並將異步操做的結果返回給ret.value
                ret = gen.next(res);
            } catch (e) {
                // 若報錯,直接調用reject返回外圍Promise對象的狀態,並傳出錯誤對象
                return reject(e);
            }
            // 將gen.next的執行結果傳入next函數,實現依次串行調用gen.next方法
            next(ret);
            return null;
        }

        // 當作全部內部Promise對象的reject方法,處理異步結果,並繼續調用下一個Promise
        function onRejected(err) {
            var ret;
            try {
                ret = gen.throw(err);
            } catch (e) {
                // 若報錯,直接調用reject返回外圍Promise對象的狀態,並傳出錯誤對象
                return reject(e);
            }
            // 將gen.throw的執行結果傳入next函數,實現依次串行調用gen.next方法
            next(ret);
        }
        
        // 實現串行調用gen.next的核心
        function next(ret) {
            // 判斷內部Promise是否所有執行完畢
            // 若執行完畢,直接調用resolve改變外圍Promise的狀態,並返回最終的return值[問題3]
            if (ret.done) return resolve(ret.value);
            // 若未執行完畢,調用toPromise方法將上一個Promise返回的值轉化爲Promise對象
            // 具體參見toPromise方法
            var value = toPromise.call(ctx, ret.value);
            // 根據value轉化後的Promise對象的兩個狀態,執行下一個next方法
            if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
            // 拋出不符合轉化規則的類型的值
            return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
                + 'but the following object was passed: "' + String(ret.value) + '"'));
            }
        });
    }

源碼分析完了,咱們能夠把co串行調用generator函數中yield的過程總結以下:

  1. 進入外圍Promise

  2. 經過入口onFilfilled()方法,將generator函數運行至第一個yield處,執行該yield後邊的異步操做,並將結果傳入next方法

  3. 若是next中傳入結果的done爲true,則返回外圍Promise的resolve

  4. 若是next中傳入結果的done爲true,則返回value(即yield後邊的對象)是否能夠轉化爲內部Promise對象。如沒法轉化,則拋出錯誤,返回外圍Promise的reject

  5. 若能轉化爲Promise對象,將全部內部Promise並行執行,經過then(onFilfilled, onRejected)開始執行

  6. 在onFilfilled()或者onRejected()內部調用再次調用next()方法,實現串行執行yield,並肩yield後邊的對象傳遞給next(),依次重複。

  7. 全部yield執行返回,將最後的return值返回給外圍Promise的resovle方法,結束co對generator函數的調用

yield後面對象轉化爲Promise

可以在co中實現generator函數的逐步調用next()方法,轉化爲內部Promise將相當重要,而源碼是如何轉化的呢?哪些對象又是可以轉化的呢?接下來,咱們看下源碼。

function toPromise(obj) {
    // 確保obj有意義
    if (!obj) return obj;
    // 如果Promise對象,則直接返回
    if (isPromise(obj)) return obj;
    // 如果generator函數或者generator對象,則傳入一個新的co,並返回新co的外圍Promise
    // 做爲當前co的內部Promise,這樣實現多層級調用
    if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
    // 如果函數,則返回thunk規範的函數
    if ('function' == typeof obj) return thunkToPromise.call(this, obj);
    // 如果數組,把數組中每一個元素轉化爲內部Promise,返回Promise.all並行運算
    if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
    // 如果對象,遍歷對象中的每一個key對應的value,轉化成Promise.all並行運算
    if (isObject(obj)) return objectToPromise.call(this, obj);
    return obj;
}

function thunkToPromise(fn) {
  var ctx = this;
  return new Promise(function (resolve, reject) {
    fn.call(ctx, function (err, res) {
      if (err) return reject(err);
      if (arguments.length > 2) res = slice.call(arguments, 1);
      resolve(res);
    });
  });
}

function arrayToPromise(obj) {
    // Array.map並行計算返回每個元素的Promise
    return Promise.all(obj.map(toPromise, this));
}

function objectToPromise(obj){
  var results = new obj.constructor();
  var keys = Object.keys(obj);
  var promises = [];
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    var promise = toPromise.call(this, obj[key]);
    if (promise && isPromise(promise)) defer(promise, key);
    else results[key] = obj[key];
  }
  // Promise鏈式調用,後續的then能偶獲取此處的results
  return Promise.all(promises).then(function () {
    return results;
  });

  function defer(promise, key) {
    // key對應的元素成功轉化爲Promise對象後,構造這些Promise的resovle方法
    // 以便在results中獲取每一個Promise對象成功執行後結果
    results[key] = undefined;
    promises.push(promise.then(function (res) {
      results[key] = res;
    }));
  }
}

結合上述分析,咱們能夠獲得,yield後面只能是函數、Promise對象、Generator函數、Generator迭代器對象、數組(元素僅限以前的4類)和Object(對應value僅限定以前的4類)

相關文章
相關標籤/搜索