洋葱模型指中间件执行呈“先外向内、再内向外”的嵌套结构:请求时依次执行各中间件的前置逻辑,到达最内层后,再逐层返回执行后置逻辑。其核心依赖async/await暂停恢复机制与next递归调用实现。

await 本身不实现洋葱圈模型,但配合 async/await 和数组递归调用,可以优雅地实现中间件的“洋葱式”执行流程。核心在于:每个中间件既能处理请求前逻辑(外层),也能处理响应后逻辑(内层),而 await 让我们能自然地在异步操作前后插入代码。
洋葱圈模型长什么样?
所谓“洋葱圈”,是指中间件执行顺序像剥洋葱:从外向内进入(request 阶段),到达最内层后,再从内向外返回(response 阶段)。例如:
- logger → auth → route → auth(后置)→ logger(后置)
关键特征是:每个中间件的 后半段逻辑在 await 下一个中间件完成之后才执行,这正是 await 的天然优势。
基本实现结构
中间件函数需满足统一签名:async (ctx, next) => {...},其中 next 是调用下一个中间件的函数。最内层的 next 通常是一个空的 Promise.resolve() 或实际业务处理器。
示例代码:
const compose = (middlewares) => {
return async (ctx) => {
const dispatch = (i) => {
if (i >= middlewares.length) return Promise.resolve();
const middleware = middlewares[i];
return middleware(ctx, () => dispatch(i + 1));
};
return dispatch(0);
};
};
// 使用
const stack = [
async (ctx, next) => {
console.log('1: before');
await next();
console.log('1: after');
},
async (ctx, next) => {
console.log('2: before');
await next();
console.log('2: after');
},
async (ctx, next) => {
console.log('3: before');
ctx.body = 'done';
// 不再调用 next,终止链路
}
];
compose(stack)({});
// 输出:
// 1: before
// 2: before
// 3: before
// 2: after
// 1: after
为什么 await 能支撑这个模型?
- 暂停与恢复:await 会暂停当前 async 函数执行,等 Promise settle 后继续——这正好对应“进入下一层,等它回来”的语义。
-
作用域隔离:每个中间件的
await next()后面的代码,自动处于“返回路径”上,无需手动维护栈或回调嵌套。 -
错误冒泡天然支持:任意中间件抛错,会沿 await 链向上被外层
try/catch捕获,便于统一错误处理。
实际项目中的常见变体
-
带中断能力:某个中间件不调用
next()(如权限拒绝),后续中间件直接跳过。 -
上下文透传:通过
ctx对象共享数据(如用户信息、日志 ID),避免闭包污染。 -
错误处理中间件:放在栈末尾,用
try/catch包裹await next(),捕获内层所有异常。 -
Koa 风格:Koa 的
use和koa-compose就是该模式的成熟实现,原理完全一致。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











