koa-compose 源码分析

780 阅读2分钟

koa 的核心是中间件(middleware)机制,ctx 对象经过每个中间件处理之后最终返回。我们可以借助官方的 koa-compose 来进行这种链式处理。

koa-compose 的使用

我们先来看一下一般我们是怎么去使用库的。一个最简单的获取用户列表接口:

async function findUsers(ctx) {
  try {
    ctx.body = await 调用其他方法去数据库找用户({ query: ctx.request.query })
  } catch (err) {
    ctx.status = 404
    ctx.body = { err, message: err.message }
  }
}

我们希望对接口的查询参数进行检查,并且用来检查的代码应该是可以通用的,抽象出一个 validatorMiddleware :

async function validatorMiddleware(ctx, next) {
    // 具体验证查询参数的做法我就不写了~
    await next() // 调用下一个中间件
}

当我们在 export 这个接口时会借助 koa-compose 这样做:

const compose = require('koa-compose')

module.exports = compose([
  validatorMiddleware,
  findUsers
])

源码

那么 koa-compose 是怎么实现对多个中间件进行合并的呢? koa-compose 的源码其实也很简单,一共只有几十行(源码地址:github.com/koajs/compo… ):

'use strict'

/**
 * Expose compositor.
 */

module.exports = compose

/**
 * Compose `middleware` returning
 * a fully valid middleware comprised
 * of all those which are passed.
 *
 * @param {Array} middleware
 * @return {Function}
 * @api public
 */

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}
  1. 使用 index 变量缓存中间件调用次数,防止某些中间件内多次调用 next():
let index = -1
return dispatch(0)
function dispatch (i) {
  if (i <= index) return Promise.reject(new Error('next() called multiple times'))
  ...
}
  1. 通过递归的方式使得 middleware 数组里的中间件被依次调用:
function dispatch (i) {
  ...
  let fn = middleware[i]
  ...
  try {
    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
  } catch (err) {
    return Promise.reject(err)
  }
}
  1. 根据数组下标来判断已经调用到最后一个中间件时(没有下一个中间件了),返回一个 的 fulfilled 状态的 Promise 对象,返回上一层的中间件(函数出栈):
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()

参考

带你走进koa2的世界(koa2源码浅谈):imhjm.com/article/591…