手写支持异步的 Koa 洋葱模型 compose,并防止多次 next。
先理解原理和示例,再练习完整口述与追问。
回答要点
递归 dispatch;Promise 采纳;每次请求独立游标;同步异常转拒绝;next 只前进一次;中间件要 await/return。
原理拆解
同步函数 compose 把值从一步传给下一步,洋葱组合却把“执行下游的能力”作为 next 传入当前中间件。await next() 暂停当前后半段,等下游完成后恢复,因此能反向退出。每次执行创建自己的 last 游标,拒绝对已执行层级的二次 dispatch,避免一个中间件重复 next 导致响应和副作用执行多次。
示例与推演
function composeMiddleware(middlewares) {
if (!Array.isArray(middlewares) || !middlewares.every(fn => typeof fn === 'function')) {
throw new TypeError('中间件必须是函数数组')
}
return function run(context) {
let last = -1
function dispatch(index) {
if (index <= last) return Promise.reject(new Error('next 不能重复调用'))
last = index
const middleware = middlewares[index]
if (!middleware) return Promise.resolve()
try {
return Promise.resolve(middleware(context, () => dispatch(index + 1)))
} catch (error) {
return Promise.reject(error)
}
}
return dispatch(0)
}
}
A 写 A1、await next、写 A2,B 写 B1、await next、写 B2,执行得到 A1 B1 B2 A2。单次遍历额外空间随链深度 O(n),中间件自身工作另计;大量同步 next 嵌套仍要留意栈限制。
易错点
示例不包含 Koa 完整 ctx、响应和外部 next 协议。中间件忘记 await/return 下游时,组合器无法替调用方恢复正确的等待与错误捕获。
参考回答可直接练习口述
我用 dispatch 把下游封成 next,统一用 Promise 采纳同步和异步结果,当前层等待后形成反向返回。每次执行独立记录已到层级,阻止重复 next,并测试顺序、异常和并发请求隔离。