koa-convert 源码解析

Posted 小平果118

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了koa-convert 源码解析相关的知识,希望对你有一定的参考价值。

npm:https://www.npmjs.com/package/koa-convert

我们在迁移一些koa2的服务的时候,或者做一些node-server框架升级的时候,为了兼容koa1中的一些yield写法,不得不将老的yield- generator函数转化为koa2的async/await函数来执行。中间有两个问题,需要解决:

  • 如何将generator函数转化为Promise函数;
  • 如何让generator函数暂停器,执行下去到下个中间件执行;

有一个专门的koa-convert函数封装了,内部功能,仅仅60行代码,即可简单实现互转;

koa-convert

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
  
    
  // co将Generator函数转换成promise对象,然后会自动执行该函数的代码
  const converted = function (ctx, next) 
     // return 一个Promise函数, 直接调用该Generator函数,返回return yield 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))

部分源码如上,首先引入co包中的代码,co的作用是:将Generator函数转换成promise对象,然后会自动执行该函数的代码。

引入 koa-compose包,将koa包中的中间件合并,然后依次执行各个中间件。

convert 函数

convert函数有一个参数mw,首先判断该参数mw是不是一个函数,如果该mw不是一个函数的话,就直接抛出一个异常提示,该中间件必须是一个函数。

判断该 mw.constructor.name !== 'GeneratorFunction' 是不是一个Generator函数,如果不是Generator函数的话,就直接返回该mw。

如果它是Generator函数的话,就会执行 converted 函数,该函数有2个参数,第一个参数ctx是运行Generator的上下文,第二个参数是传递给Generator函数的next参数。

最后返回 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对象。该本身对象的代码会自动执行完。

在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对象了。

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

代码也是一样判断:

判断mw是否是一个函数,如果不是一个函数,则抛出异常。判断mw.constructor.name === 'GeneratorFunction'; 如果是Generator函数的话,就直接返回该Generator函数。
如果不是Generaror函数的话,就执行 converted 方法,转换成Generator函数。同样的道理调用co模块返回一个Promise对象。

compose函数

代码如下:

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

该函数的作用是:就是将一系列Generator函数组成的数组,直接转成Koa2中可执行的middleware形式。调用 koa-compose 包转换成中间件形式。

有了这个小小的包,就可以愉快的玩耍了。迁移无忧~~

以上是关于koa-convert 源码解析的主要内容,如果未能解决你的问题,请参考以下文章

SynchronousQueue 源码解析

Spring 源码解析之DispatcherServlet源码解析

Spring 源码解析之DispatcherServlet源码解析

Spring 源码解析之DispatcherServlet源码解析

Android源码解析——LruCache

#yyds干货盘点# Collection - ArrayList 源码解析