题目
实现一个中间件执行流程的 compose 函数
信息
- 类型:问答
- 难度:⭐⭐
考点
中间件原理, 洋葱模型, 异步流程控制
快速回答
实现要点:
- 使用递归或迭代处理中间件队列
- 每个中间件接收
context和next参数 - 通过
dispatch函数控制执行流程 - 支持异步中间件(返回 Promise)
原理说明
中间件核心是洋葱模型(Onion Model),请求从外到内穿透中间件栈,响应从内到外返回。关键是通过 compose 函数将多个中间件组合成嵌套结构:
middleware1(ctx, () => middleware2(ctx, () => middleware3(ctx, next)))代码实现
递归版 compose 函数:
function compose(middlewares) {
return function (ctx, next) {
let index = -1
function dispatch(i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middlewares[i]
if (i === middlewares.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
return dispatch(0)
}
}使用示例
// 定义中间件
const m1 = async (ctx, next) => {
console.log('m1 start')
await next()
console.log('m1 end')
}
const m2 = async (ctx, next) => {
console.log('m2 start')
await next()
console.log('m2 end')
}
// 组合执行
const fn = compose([m1, m2])
fn({}).then(() => {
console.log('执行完成')
})
/* 输出顺序:
m1 start
m2 start
m2 end
m1 end
执行完成
*/最佳实践
- 错误处理:在最外层添加 catch 中间件
- 性能优化:迭代实现避免递归调用栈溢出
- 上下文设计:通过 ctx 对象共享请求/响应数据
常见错误
- 忘记调用
next()导致后续中间件不执行 - 多次调用
next()引发执行顺序错乱 - 未正确处理异步操作(缺少 async/await)
扩展知识
- Koa 中间件原理:基于 compose 库实现,支持洋葱模型
- Express 差异:线性执行(非洋葱模型),通过 next 手动控制
- 高级应用:中间件可用于日志、鉴权、数据校验等横切关注点