实现中间件机制
手写 Koa 风格的中间件机制,理解洋葱模型
问题
实现一个简单的中间件机制(Koa style),支持洋葱模型和异步中间件。
解答
中间件组合函数 compose
function compose(middlewares) {
// 返回一个接收 context 的函数
return function (ctx) {
// 从第一个中间件开始执行
return dispatch(0);
function dispatch(i) {
// 取出当前中间件
const fn = middlewares[i];
// 所有中间件执行完毕
if (!fn) {
return Promise.resolve();
}
try {
// 执行中间件,传入 ctx 和 next 函数
// next 函数就是执行下一个中间件
return Promise.resolve(fn(ctx, () => dispatch(i + 1)));
} catch (err) {
return Promise.reject(err);
}
}
};
}
简易 Koa 实现
class Koa {
constructor() {
this.middlewares = [];
}
// 添加中间件
use(fn) {
this.middlewares.push(fn);
return this; // 支持链式调用
}
// 组合并执行所有中间件
callback() {
const fn = compose(this.middlewares);
return (ctx) => fn(ctx);
}
}
测试洋葱模型
const app = new Koa();
// 中间件 1
app.use(async (ctx, next) => {
console.log('1 - 进入');
ctx.body = '1';
await next();
console.log('1 - 离开');
});
// 中间件 2
app.use(async (ctx, next) => {
console.log('2 - 进入');
ctx.body += '2';
await next();
console.log('2 - 离开');
});
// 中间件 3
app.use(async (ctx, next) => {
console.log('3 - 进入');
ctx.body += '3';
await next();
console.log('3 - 离开');
});
// 执行
const ctx = { body: '' };
app.callback()(ctx).then(() => {
console.log('结果:', ctx.body);
});
// 输出:
// 1 - 进入
// 2 - 进入
// 3 - 进入
// 3 - 离开
// 2 - 离开
// 1 - 离开
// 结果: 123
异步中间件示例
const app = new Koa();
app.use(async (ctx, next) => {
const start = Date.now();
await next();
console.log(`耗时: ${Date.now() - start}ms`);
});
app.use(async (ctx, next) => {
// 模拟异步操作
await new Promise((resolve) => setTimeout(resolve, 100));
ctx.body = 'Hello';
await next();
});
const ctx = {};
app.callback()(ctx).then(() => {
console.log(ctx.body); // Hello
});
// 输出: 耗时: 100ms
关键点
- 洋葱模型:中间件按顺序进入,逆序离开,形成洋葱状的执行流程
- next 函数:调用 next() 会暂停当前中间件,执行下一个,完成后再返回
- Promise 包装:用 Promise.resolve 包装确保同步和异步中间件都能正确处理
- 递归调度:dispatch 函数通过递归实现中间件的串联执行
- ctx 共享:所有中间件共享同一个 context 对象,可以传递数据
目录