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