中间件机制
1. Express vs Koa 中间件模型
核心区别
| 特性 | Express | Koa |
|---|---|---|
| 模型 | 线性 (回调) | 洋葱 (Promise) |
next() 返回值 | 无 | Promise |
| async/await | 需手动处理 | 原生支持 |
| 后置逻辑 | 困难 | await next() 后执行 |
Express 线性模型
javascript
app.use((req, res, next) => {
console.log('1. 进入');
next(); // 不会等待后续中间件
console.log('2. 退出'); // 立即执行,不保证顺序
});
app.use(async (req, res, next) => {
await someAsyncOperation(); // Express 不会等待!
next();
});WARNING
Express 中的异步中间件,如果不在回调中显式处理,可能导致响应提前返回。
Koa 洋葱模型
javascript
app.use(async (ctx, next) => {
console.log('1. 进入 A');
await next(); // 暂停,进入下一层
console.log('6. 退出 A'); // 内层全部完成后执行
});
app.use(async (ctx, next) => {
console.log('2. 进入 B');
await next();
console.log('5. 退出 B');
});
app.use(async (ctx, next) => {
console.log('3. 进入 C');
console.log('4. 退出 C');
});
// 输出: 1 → 2 → 3 → 4 → 5 → 6 ┌─────────────────────────────────┐
│ Middleware A │
│ ┌─────────────────────────┐ │
│ │ Middleware B │ │
│ │ ┌─────────────────┐ │ │
Request │ │ │ Middleware C │ │ │ Response
───────▶│ │ │ │ │ │────────▶
│ │ │ │ │ │
│ │ └─────────────────┘ │ │
│ └─────────────────────────┘ │
└─────────────────────────────────┘2. Compose 函数实现 🔥
核心原理
javascript
function compose(middleware) {
return function (context, next) {
// index 防止多次调用 next()
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];
// 最后一个中间件后调用外部 next
if (i === middleware.length) fn = next;
if (!fn) return Promise.resolve();
try {
// Promise.resolve 确保返回 Promise (兼容同步函数)
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err);
}
}
};
}关键考察点
- 递归调用:
dispatch(i + 1)实现链式调用 - 闭包状态:
index闭包变量追踪执行进度 - Promise 包装:
Promise.resolve()确保异步兼容 - 防重复调用:
i <= index检测
使用示例
javascript
const middlewares = [
async (ctx, next) => {
ctx.data = [];
ctx.data.push(1);
await next();
ctx.data.push(6);
},
async (ctx, next) => {
ctx.data.push(2);
await next();
ctx.data.push(5);
},
async (ctx, next) => {
ctx.data.push(3);
await new Promise(r => setTimeout(r, 100));
ctx.data.push(4);
}
];
const fn = compose(middlewares);
const ctx = {};
await fn(ctx);
console.log(ctx.data); // [1, 2, 3, 4, 5, 6]3. 实际应用场景
请求耗时统计
javascript
app.use(async (ctx, next) => {
const start = Date.now();
await next(); // 等待所有后续中间件
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});错误处理
javascript
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = { error: err.message };
ctx.app.emit('error', err, ctx);
}
});条件中间件
javascript
function unless(path, middleware) {
return async (ctx, next) => {
if (ctx.path.startsWith(path)) {
return next(); // 跳过此中间件
}
return middleware(ctx, next);
};
}
app.use(unless('/public', authMiddleware));