koa-convert原始碼分析

龍恩0707發表於2019-03-12

koa-convert最主要的作用是:將koa1包中使用的Generator函式轉換成Koa2中的async函式。更準確的說是將Generator函式轉換成使用co包裝成的Promise物件。然後執行對應的程式碼。當然該包中也提供了back方法,也可以把koa2中的async函式轉換成koa1包中的Generator函式。

首先我們來看下使用Koa1中使用Generator函式和Koa2中使用的async函式的demo程式碼如下:

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.

我們再來看下,Koa原始碼中的application.js 程式碼如下:在use方法內部,程式碼如下:

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中的原始碼是如何做的呢?

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包中的中介軟體合併,然後依次執行各個中介軟體。

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函式程式碼如下:

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

因此 mw.call(ctx, createGenerator(next)),如果mw是一個Generator函式的話,就直接呼叫該Generator函式,返回return yield next(); 返回下一個中介軟體,然後使用呼叫co包,使返回一個Promise物件。該本身物件的程式碼會自動執行完。

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 包轉換成中介軟體形式。

相關文章