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)
}
}
}
}
- 使用 index 变量缓存中间件调用次数,防止某些中间件内多次调用 next():
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
...
}
- 通过递归的方式使得 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)
}
}
- 根据数组下标来判断已经调用到最后一个中间件时(没有下一个中间件了),返回一个 的 fulfilled 状态的 Promise 对象,返回上一层的中间件(函数出栈):
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
参考
带你走进koa2的世界(koa2源码浅谈):imhjm.com/article/591…