← 返回题目列表

Express/Koa 中间件洋葱模型是什么?

高频 中等 第 1 / 27 题 更新于 2026/07/27
Node.jsExpressKoa中间件洋葱模型

简化版

中间件是请求处理链上的函数。Koa 的洋葱模型通过 await next() 实现前置逻辑和后置逻辑:请求进入时从外到内执行,响应返回时从内到外执行。Express 更偏顺序回调链。

详细版

Koa 示例:

app.use(async (ctx, next) => {
  console.log('A before');
  await next();
  console.log('A after');
});

多个中间件执行顺序类似:

A before -> B before -> handler -> B after -> A after

适合做日志、鉴权、错误处理、响应时间统计、跨域、压缩等。

完整版教学

一、中间件是请求处理管线

一个 HTTP 请求进入应用后,通常要经过很多步骤:解析 body、处理 CORS、鉴权、路由、业务逻辑、错误处理、日志记录。中间件把这些横切逻辑拆成可组合函数。

这样业务 handler 不必关心所有通用细节。

二、Koa 洋葱模型的关键是 await next

await next() 前是进入下一层之前的逻辑,之后是下一层完成后的逻辑。这使得外层中间件可以包裹内层执行。

比如统计耗时:

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  ctx.set('X-Time', Date.now() - start);
});

三、Express 和 Koa 的差异

Express 中间件基于 req, res, next,更偏回调风格,生态成熟。Koa 更轻,基于 async/await,ctx 封装更清晰,洋葱模型更自然。

Express 也能写后置逻辑,但错误处理和异步传递方式与 Koa 不完全一样。

四、面试追问与工程落地

常见追问是“错误处理中间件放哪里”。通常错误处理要放在外层,包住后续中间件,这样内部 throw 的错误能被统一捕获并转成响应。

工程中中间件顺序非常重要。body parser 必须在需要读取 body 的路由前,鉴权应在受保护路由前,错误处理中间件要包住业务链路。顺序错了,功能就会诡异失效。

五、await next 决定控制权是否真正回来

Koa 的 next() 返回下游中间件完成的 Promise。外层只有 await next(),才能在下游响应或抛错之后恢复执行;漏掉 await 会让外层提前结束,耗时统计偏小,try/catch 也可能接不到后续 rejection。

A before
  └─ await B before
       └─ await handler
       └─ B after
  └─ A after

假设 B 和 handler 分别耗时 20 ms、30 ms,A 正确包裹时统计约 50 ms;若 A 只调用 next() 不等待,可能立即记录接近 0 ms。洋葱模型不是日志打印技巧,而是 Promise 调用栈的进入和回溯。

场景KoaExpress 5
普通中间件async (ctx, next)(req, res, next)
继续下游await next()next() 或返回 Promise handler
异步 throw/reject外层 try/catch 可捕获返回的 rejected Promise 自动交给错误链
错误中间件位置外层先注册以包住后续四参数错误中间件通常放路由之后

六、顺序、终止条件和响应已发送边界

中间件既可以继续下游,也可以直接结束请求。鉴权失败时设置 401 后应 return,不能继续让业务 handler 写第二次响应;Express 中调用 next(err) 会跳过普通中间件进入错误链,错误处理中间件必须保持四参数签名。

Express 5 已能自动转发返回 Promise 的 rejection;Express 4 项目常需要包装 async handler 或显式 .catch(next)。无论框架版本,启动后台 Promise 却不 return/await,都游离在请求链外,统一错误中间件无法可靠接管。

当响应头已经发送后才发生流式错误,不能再改成一份完整 JSON 错误体;Express 自定义错误处理中间件应检查 headersSent 并交给默认处理关闭连接。Koa 也要区分响应尚未提交与 socket 已部分写出的情况。

中间件排查顺序:先看注册位置,再看 next 是否调用和等待,最后看响应是否已经结束;三者决定控制流,而不是函数名。

七、常见误区与追问

  • 误区:调用 next() 就等于 await next()。 前者只启动下游,后者才等待下游完成并恢复后置逻辑。
  • 误区:Express 完全没有洋葱式后置逻辑。 Promise/回调之后仍可写后置代码,但控制流和错误约定与 Koa 不同。
  • 误区:错误中间件放在最前面才能捕获 Express 路由错误。 Express 错误链按注册顺序向后寻找四参数处理器,通常应放路由之后。
  • 追问:Koa 错误处理中间件为什么先注册? 它要作为外层 try { await next() } 包住后续整条链。
  • 追问:Express 5 对 async handler 有什么变化? 返回 Promise 的 throw/rejection 会自动调用错误流程,Express 4 不能一概类推。
  • 追问:为什么日志中间件适合放外层? 它可以覆盖解析、鉴权、路由和业务的总耗时及最终状态。
  • 追问:中间件怎样主动终止链? 不调用 next,并明确设置响应;之后应 return,避免继续写响应或业务副作用。

八、加强记忆

中间件是“请求流水线”,Koa 洋葱模型是“进去一层层 before,出来一层层 after”。await next() 是分界线。Express 还要记住顺序决定匹配与错误传播,Express 5 只会自动转发处理器返回的 Promise rejection。