Koa 源码中 koa-compose 的洋葱模型实现
请手写实现 koa-compose 函数,解释其如何在 Koa 中实现中间件的洋葱模型(await next() 回溯执行)。
回答
屠龙少年
koa-compose 精简实现:
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!');
}
return function (context, next) {
let index = -1;
return dispatch(0);
function dispatch(i) {
// 防止 next() 被重复调用
if (i <= index) {
return Promise.reject(new Error('next() called multiple times'));
}
index = i;
let fn = middleware[i];
// 到达最后一个中间件后,执行外部传入的 next(若有)
if (i === middleware.length) fn = next;
// 没有更多中间件,直接 resolve
if (!fn) return Promise.resolve();
try {
// 执行中间件,传入 dispatch(i+1) 作为 next
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err);
}
}
};
}
工作流程示例:
const middleware = [
async (ctx, next) => {
console.log('1 进入');
await next();
console.log('1 离开');
},
async (ctx, next) => {
console.log('2 进入');
await next();
console.log('2 离开');
},
async (ctx, next) => {
console.log('3 处理');
ctx.body = 'Hello';
},
];
const fn = compose(middleware);
fn({});
// 输出:
// 1 进入
// 2 进入
// 3 处理
// 2 离开
// 1 离开
关键设计:
Promise.resolve(fn(ctx, dispatch(i+1)))确保所有中间件都是 Promiseindex变量防止同一中间件内多次调用 next()fn = next允许外部传递最后一个中间件的后续处理- try/catch 将同步异常转为 Promise reject