Skip to content

express 洋葱模型

Express 的洋葱模型是对中间件(Middleware)执行流程的形象比喻,核心特点是:中间件按照“先进后出”的顺序执行,请求进入时依次经过中间件,响应返回时则反向经过中间件,类似洋葱从外层到内层再回到外层的穿透过程。

直观理解洋葱模型

假设我们有 3 个中间件 ABC,按顺序注册到 Express 中:

javascript
const express = require('express');
const app = express();

// 中间件 A
app.use((req, res, next) => {
  console.log('A 开始');
  next(); // 调用 next() 进入下一个中间件
  console.log('A 结束');
});

// 中间件 B
app.use((req, res, next) => {
  console.log('B 开始');
  next();
  console.log('B 结束');
});

// 中间件 C(路由处理)
app.use((req, res) => {
  console.log('C 处理请求');
  res.send('Hello World');
  console.log('C 结束');
});

app.listen(3000);

当有请求到达时,输出顺序为:

A 开始 → B 开始 → C 处理请求 → C 结束 → B 结束 → A 结束

这个流程就像洋葱:

  • 请求进入时(从外到内):ABC
  • 响应返回时(从内到外):CBA

核心原理

  1. 中间件的注册顺序即执行顺序:Express 按 app.use() 的调用顺序维护一个中间件队列,请求到达时依次执行。
  2. next() 函数的作用
    • 调用 next() 会暂停当前中间件,移交控制权给下一个中间件。
    • 当后续中间件执行完毕(或没有更多中间件),会回溯到上一个中间件 next() 之后的代码继续执行。
  3. 异步支持:如果中间件包含异步操作(如数据库查询),需在异步操作完成后调用 next(),确保洋葱模型的顺序性:
    javascript
    app.use(async (req, res, next) => {
      console.log('A 开始');
      await db.query(/* 异步操作 */); // 等待异步完成
      next(); // 进入下一个中间件
      console.log('A 结束');
    });

为什么需要洋葱模型?

  • 统一的请求/响应处理:例如,在最外层中间件处理日志记录(A 开始 记录请求开始,A 结束 记录请求耗时)。
  • 共享数据与流程控制:中间件可以在 reqres 对象上挂载数据(如用户信息),后续中间件可读取,回溯时还能修改响应。
  • 错误处理:错误处理中间件(带 4 个参数 (err, req, res, next))会捕获后续中间件抛出的错误,通过洋葱模型回溯到错误处理逻辑。

总结

Express 的洋葱模型本质是中间件的嵌套执行机制,通过 next() 函数实现“进入-回溯”的流程。这种设计让请求/响应的生命周期管理更灵活,便于实现日志、认证、错误处理等跨切面功能。