koa-convert源碼分析

koa-convert最主要的做用是:將koa1包中使用的Generator函數轉換成Koa2中的async函數。更準確的說是將Generator函數轉換成使用co包裝成的Promise對象。而後執行對應的代碼。固然該包中也提供了back方法,也能夠把koa2中的async函數轉換成koa1包中的Generator函數。html

首先咱們來看下使用Koa1中使用Generator函數和Koa2中使用的async函數的demo代碼以下:node

const Koa = require('koa');
const app = new Koa();

// koa1 使用generator函數的寫法
app.use(function *(next) {
  console.log(1111); // 1. 第一步先打印 1111
  yield next;
  console.log(222222); // 4. 第四步打印 222222
});

// koa2的寫法
app.use(async (ctx, next) => {
  console.log(3333); // 2. 第二步再打印 3333
  await next();
  console.log(44444); // 3. 第三部打印44444
});


app.listen(3001);
console.log('app started at port 3001...');

當咱們在node命令行中使用 node app.js 命令時,而後瀏覽器中 輸入地址:http://localhost:3001/ 訪問的時候,咱們能夠看到命令中會分別打印 1111 3333 444444 222222.git

咱們再來看下,Koa源碼中的application.js 代碼以下:在use方法內部,代碼以下:github

const convert = require('koa-convert');
use(fn) {
  if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
  if (isGeneratorFunction(fn)) {
    deprecate('Support for generators will be removed in v3. ' +
              'See the documentation for examples of how to convert old middleware ' +
              'https://github.com/koajs/koa/blob/master/docs/migration.md');
    fn = convert(fn);
  }
  debug('use %s', fn._name || fn.name || '-');
  this.middleware.push(fn);
  return this;
}

如上koa2源碼中的use函數,該函數有一個fn參數,首先判斷該fn是否是一個函數,若是不是一個函數的話,直接拋出一個錯誤,提示,中間件必須爲一個函數。第二步繼續判斷該fn函數是否是一個Generator函數,若是它是generator函數的話,就把該函數使用 koa-convert包轉換成async函數。而後把對應的async函數放進 this.middleware數組中。最後返回該對象this。數組

這上面是koa2中的基本源碼,下面咱們來看看 koa-convert中的源碼是如何作的呢?promise

const co = require('co')
const compose = require('koa-compose')

module.exports = convert

function convert (mw) {
  if (typeof mw !== 'function') {
    throw new TypeError('middleware must be a function')
  }
  if (mw.constructor.name !== 'GeneratorFunction') {
    // assume it's Promise-based middleware
    return mw
  }
  const converted = function (ctx, next) {
    return co.call(ctx, mw.call(ctx, createGenerator(next)))
  }
  converted._name = mw._name || mw.name
  return converted
}

function * createGenerator (next) {
  return yield next()
}

// convert.compose(mw, mw, mw)
// convert.compose([mw, mw, mw])
convert.compose = function (arr) {
  if (!Array.isArray(arr)) {
    arr = Array.from(arguments)
  }
  return compose(arr.map(convert))
}

1. 部分源碼如上,首先引入co包中的代碼,要深刻了解co包中源碼請看這篇文章, 該co的做用是:將Generator函數轉換成promise對象,而後會自動執行該函數的代碼。瀏覽器

2. 引入 koa-compose包,要深刻了解 koa-compose包,請看這篇文章. 該包的做用是:將koa包中的中間件合併,而後依次執行各個中間件。app

3. convert 函數
1. 該函數有一個參數mw,首先判斷該參數mw是否是一個函數,若是該mw不是一個函數的話,就直接拋出一個異常提示,該中間件必須是一個函數。
2. 判斷該 mw.constructor.name !== 'GeneratorFunction' 是否是一個Generator函數,若是不是Generator函數的話,就直接返回該mw。
3. 若是它是Generator函數的話,就會執行 converted 函數,該函數有2個參數,第一個參數ctx是運行Generator的上下文,第二個參數是傳遞給Generator函數的next參數。
4. 最後返回 return co.call(ctx, mw.call(ctx, createGenerator(next))); co的做用是介紹一個Generator函數,而後會返回一個Promise對象,而後該Generator函數會自動執行。createGenerator函數代碼以下:koa

function * createGenerator (next) {
   return yield next()
}

所以 mw.call(ctx, createGenerator(next)),若是mw是一個Generator函數的話,就直接調用該Generator函數,返回return yield next(); 返回下一個中間件,而後使用調用co包,使返回一個Promise對象。該自己對象的代碼會自動執行完。async

5. 在convert函數代碼中,有一句代碼 mw.constructor.name !== 'GeneratorFunction' 是否是一個Generator函數。
能夠如上面進行判斷,好比以下代碼演示是不是Generator函數仍是AsyncFunction函數了,以下代碼:

function* test () {};
console.log(test.constructor.name); // 打印 GeneratorFunction

async function test2() {};
console.log(test2.constructor.name); // 打印 AsyncFunction

如上全部的分析是 convert 函數的代碼了,該代碼一個最主要的做用,判斷傳遞進來的mw參數是否是Generator函數,若是是Generator函數的話,就把該Generator函數轉化成使用co包裝成Promise對象了。
4. back函數代碼以下:

convert.back = function (mw) {
  if (typeof mw !== 'function') {
    throw new TypeError('middleware must be a function')
  }
  if (mw.constructor.name === 'GeneratorFunction') {
    // assume it's generator middleware
    return mw
  }
  const converted = function * (next) {
    let ctx = this
    let called = false
    // no need try...catch here, it's ok even `mw()` throw exception
    yield Promise.resolve(mw(ctx, function () {
      if (called) {
        // guard against multiple next() calls
        // https://github.com/koajs/compose/blob/4e3e96baf58b817d71bd44a8c0d78bb42623aa95/index.js#L36
        return Promise.reject(new Error('next() called multiple times'))
      }
      called = true
      return co.call(ctx, next)
    }))
  }
  converted._name = mw._name || mw.name
  return converted
}

代碼也是同樣判斷:
1. 判斷mw是不是一個函數,若是不是一個函數,則拋出異常。
2. 判斷mw.constructor.name === 'GeneratorFunction'; 若是是Generator函數的話,就直接返回該Generator函數。
若是不是Generaror函數的話,就執行 converted 方法,轉換成Generator函數。一樣的道理調用co模塊返回一個Promise對象。

5. convert.compose函數代碼以下:

convert.compose = function (arr) {
  if (!Array.isArray(arr)) {
    arr = Array.from(arguments)
  }
  return compose(arr.map(convert))
}

該函數的做用是:就是將一系列Generator函數組成的數組,直接轉成Koa2中可執行的middleware形式。調用 koa-compose 包轉換成中間件形式。

相關文章
相關標籤/搜索