阅读 207

源码系列—koa-convert

这是我参与更文挑战的第14天,活动详情查看:更文挑战

介绍

koa-convart 是用于兼容 koa1koa2 的一个工具库。koa 0.x 以及 1.x 版本的中间件是 generator 函数形式,而 koa 2.x 的中间件是 promise 的形式。通过以下代码对比一下:

// koa 1.x 示例
var koa = require('koa');
var app = koa();

app.use(function *(next){
  var start = new Date;
  yield next;
  //再次进入中间件,记录2次通过此中间件「穿越」的时间
  var ms = new Date - start;
  this.set('X-Response-Time', ms + 'ms');
});

app.use(function *(){
  this.body = 'Hello World';
});

app.listen(3000);
//=================================================
// koa 2.x 示例
const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time', `${ms}ms`);
});

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);
复制代码

通过对比可以看到,旧版本的中间件是 generator 函数,接受一个 next 方法,而新版本的中间件则是以async 函数(本质是一个promise),接受两个参数:上下文 ctxnext 方法。

koa-convert所实现的功能就是,使 koa2 能够兼容旧版的中间件,或者使 koa1 能够使用新版的中间件,另外还提供了组合新老中间件的 api。使用示例如下:

const Koa = require('koa') // koa v2.x
const convert = require('koa-convert')
const app = new Koa()

app.use(modernMiddleware)

app.use(convert(legacyMiddleware))

app.use(convert.compose(legacyMiddleware, modernMiddleware))

function * legacyMiddleware (next) {
  // before
  yield next
  // after
}

function modernMiddleware (ctx, next) {
  // before
  return next().then(() => {
    // after
  })
}
复制代码

源码分析

koa-convert 源码代码量很少,包括注释和空行总共 105 行,核心代码只有几十行,包括三个 api方法:convert()convert.composeconvert.back

convert

我们先来看convert()的源码实现,逻辑如下:

  1. convert 方法首先判断传入的中间件是否是一个函数,如果不是就抛出异常;
  2. 接着判断是否是一个 generator 函数,如果不是就直接返回不做处理;
  3. 利用cogenerator 函数形式的中间件转成 promise 形式。

co相关知识,可以翻阅我写的源码系列—co进行过学习。

function convert (mw) {
  // 不是函数抛出异常
  if (typeof mw !== 'function') {
    throw new TypeError('middleware must be a function')
  }

   // 不是 generator 函数,直接返回
  if (
    mw.constructor.name !== 'GeneratorFunction' &&
    mw.constructor.name !== 'AsyncGeneratorFunction'
  ) {
    return mw
  }

  // 通过 co 进行转换
  const converted = function (ctx, next) {
    return co.call(
      ctx,
      mw.call(
        ctx,
        (function * (next) { return yield next() })(next) // 返回下一个中间件并作为参数传入
      ))
  }

  converted._name = mw._name || mw.name
  return converted
}
复制代码

convert.compose

接着来看 convert.compose 的实现。

convert.compose 用于将多个中间件合成一个中间件,其中使用了 koa-compose 工具库,该工具库用于将多个中间件进行洋葱模型的组合,详细知识可以通过我写的这篇源码系列—koa-compose进行学习了解。

convert.compose = function (arr) {
  // 首先判断入参形式,接受一个数组或者是多个参数
  // convert.compose(mw, mw, mw)
  // convert.compose([mw, mw, mw])
  if (!Array.isArray(arr)) {
   // 如果是多个参数就通过 Array.from 转成数组
    arr = Array.from(arguments)
  }

  // 将数组的每一项都通过 convert 转换成 promise 形式的中间件
  // 然后调用 compose 进行洋葱模型组合
  return compose(arr.map(convert))
}
复制代码

convert.back

最后看一下 convert.back的实现。

convert.back 用于将 promise 中间件转换成 generator 函数形式的中间件,使 koa2 的中间件能够在 koa1 中使用。源码逻辑如下:

  1. 判断是否是函数,不是函数就抛出错误异常;
  2. 判断是否是 generator 函数,是则直接返回;
  3. mw 包装成一个 generator 函数,使用 cogenerator 函数形式的 next 转成 promise 传给 mw
convert.back = function (mw) {
  if (typeof mw !== 'function') {
    throw new TypeError('middleware must be a function')
  }

  if (mw.constructor.name === 'GeneratorFunction' || mw.constructor.name === 'AsyncGeneratorFunction') {
    return mw
  }

  // 用 generator 函数对中间件进行封装
  const converted = function * (next) {
    const ctx = this
    let called = false  // 用于判断 next 是否被调用了两次

    yield mw(ctx, function () {
      if (called) {
        throw new Error('next() called multiple times')
      }

      called = true
      // mw 是 promise 中间件,接收的 next 也是 promise
      // 此时传递进来的 next 是 generator,需要用 co 转换一下
      return co.call(ctx, next) 
    })
  }

  converted._name = mw._name || mw.name
  return converted
}
复制代码

相关资料

源码系列—koa-compose

源码系列—co

koa-convert 源码

文章分类
前端
文章标签