【不到10行代码】🔥模拟著名框架 KOA 的洋葱模型!

大家好,我是 前端架构师 - 大卫

更多优质内容请关注微信公众号 @前端大卫

初心为助前端人🚀,进阶路上共星辰✨,

您的点赞👍与关注❤️,是我笔耕不辍的灯💡。

背景

Koa 的 洋葱模型(Onion Model) 是指其 中间件(Middleware) 的执行机制,类似于剥洋葱的过程,即 "先洋葱外层 -> 再洋葱内层 -> 执行核心逻辑 -> 退回外层" 。它的主要优点包括:

1. 更好的控制流

  • 通过 await next() 控制中间件的执行顺序,支持 前置处理后置处理,比 Express 的中间件模式更清晰。
  • 例如,可以在 next() 之前进行 请求预处理 (如参数校验、日志记录),在 next() 之后进行 响应后处理(如格式化输出、错误处理)。

2. 天然支持异步

  • Koa 中间件基于 async/await,避免了回调地狱,使代码更清晰、更易维护。
  • 在 Express 中,异步中间件需要手动 next(err) 处理错误,而 Koa 可以直接使用 try/catch 捕获异常,统一管理。

3. 错误处理更优雅

  • 由于中间件的 洋葱模型 结构,错误可以在最外层捕获,不需要在每个中间件中单独处理。
  • 例如,一个全局 try/catch 中间件就能拦截所有错误,返回统一的错误响应。

4. 代码更清晰

  • 洋葱模型的执行顺序符合直觉,可以按照 "先执行前置逻辑 -> 进入核心逻辑 -> 执行后置逻辑" 的方式编写代码,避免了 Express 复杂的 next() 处理。

5. 更强的扩展性

  • Koa 的中间件 遵循同一个规范 (基于 async/await),不同中间件可以自由组合 ,减少了 Express 中因 next() 处理不当导致的问题。
  • 方便封装 通用中间件(如日志、权限校验、错误处理、CORS、gzip 压缩等)。

🌰 示例:Koa 洋葱模型执行顺序

1. 安装 Koa

Koa 需要 Node.js v12.17.0 或更高版本,以支持 ES2015+ 及 async/await 语法。

bash 复制代码
pnpm i koa

2. 代码示例

javascript 复制代码
const Koa = require("koa");
const app = new Koa();

app.use(async (ctx, next) => {
  console.log("🧅 外层中间件 - 进入");
  await next();
  console.log("🧅 外层中间件 - 退出");
});

app.use(async (ctx, next) => {
  console.log("🔍 内层中间件 - 进入");
  await next();
  console.log("🔍 内层中间件 - 退出");
});

app.use(async (ctx) => {
  console.log("🎯 核心逻辑 - 处理请求");
  ctx.body = "Hello Koa";
});

app.listen(3000, () => console.log("Server running on http://localhost:3000"));

执行顺序(洋葱模型) 如下:

复制代码
🧅 外层中间件 - 进入
🔍 内层中间件 - 进入
🎯 核心逻辑 - 处理请求
🔍 内层中间件 - 退出
🧅 外层中间件 - 退出

模拟实现

我们将用不到 10 行代码,来实现一个简化版的 Koa 洋葱模型

JS 版本

去掉额外的 },整个 MiddlewareRunner 代码精简至 不到 10 行

核心逻辑在这一行:middleware(() => this.dispatch(i + 1))

这里传递给 middleware 的是 next 函数,本质上是一个递归调用,它会依次执行下一个中间件,直到全部完成。

js 复制代码
class MiddlewareRunner {
  #middlewares = [];
  use(middleware) {
    this.#middlewares.push(middleware);
  }
  async dispatch(i = 0) {
    const middleware = this.#middlewares[i];
    if (!middleware) return;
    await middleware(() => this.dispatch(i + 1));
  }
}

TS 版本

在 TS 版本中,我们为 middleware 添加了类型约束,以确保它是一个 async 函数,并且接收 next 作为参数。

ts 复制代码
type Middleware = (next: () => Promise<void>) => Promise<void>;

class MiddlewareRunner {
  #middlewares: Middleware[] = [];
  use(middleware: Middleware): void {
    this.#middlewares.push(middleware);
  }
  async dispatch(i = 0): Promise<void> {
    const middleware = this.#middlewares[i];
    if (!middleware) return;
    await middleware(() => this.dispatch(i + 1));
  }
}

模拟中间件执行

下面,我们来测试这个简化版 Koa。

js 复制代码
const app = new MiddlewareRunner();

app.use(async (next) => {
  console.log("🧅 外层中间件 - 进入");
  await next();
  console.log("🧅 外层中间件 - 退出");
});

app.use(async (next) => {
  console.log("🔍 内层中间件 - 进入");
  await next();
  console.log("🔍 内层中间件 - 退出");
});

app.use(async () => {
  console.log("🎯 核心逻辑 - 处理请求");
});

app.dispatch();

执行顺序

复制代码
🧅 外层中间件 - 进入
🔍 内层中间件 - 进入
🎯 核心逻辑 - 处理请求
🔍 内层中间件 - 退出
🧅 外层中间件 - 退出

可以看到,整个执行顺序完全符合 Koa 的 洋葱模型

  1. 先从外到内,依次执行中间件的 "进入" 逻辑。
  2. 核心逻辑执行完成后,再从内到外,依次执行中间件的 "退出" 逻辑。

Koa 其他实际应用场景

前置逻辑(如权限校验、日志记录)

1. 权限校验 🔐

next() 之前,检查用户是否具备访问权限。如果没有权限,返回 403,并且不执行后续中间件

js 复制代码
app.use(async (next) => {
  const user = { id: 1, role: "guest" }; // 模拟用户数据
  console.log(`👤 当前用户:ID=${user.id}, 角色=${user.role}`);

  if (user.role !== "admin") {
    console.log("❌ 访问被拒绝:需要管理员权限");
    return; // 终止请求,后续中间件不会执行
  }

  console.log("✅ 权限校验通过,继续处理请求");
  await next();
});

📌 说明:

  • user 模拟的是当前登录的用户信息。
  • 如果用户角色不是 admin,则直接拦截请求,避免执行不必要的逻辑。
  • 适用于: 管理后台接口,只允许管理员访问。

2. 请求日志记录 📝

next() 之前,记录请求的详细信息(请求方法、路径、IP 地址等)。

js 复制代码
app.use(async (next) => {
  const requestTime = new Date().toISOString();
  console.log("-----------------------------------");
  console.log(`📅 请求时间:${requestTime}`);
  console.log(`📌 请求路径:${"/api/data"}`);
  console.log(`🔍 请求方法:${"GET"}`);
  console.log(`🌍 客户端 IP:${"192.168.1.100"}`);

  await next(); // 继续执行下一个中间件

  console.log(`✅ 请求处理完成,响应时间:${new Date().toISOString()}`);
});

📌 说明:

  • 记录 请求路径、方法、IP 地址、时间 ,方便后续日志分析
  • next() 之后,再记录请求完成的时间,用于性能分析。
  • 适用于: API 日志、性能分析、流量监控

后置逻辑(如格式化响应、错误处理)

1. 统一格式化 API 响应 🎯

next() 之后,封装 API 响应结构,确保所有返回的数据格式一致。

js 复制代码
app.use(async (next) => {
  await next(); // 先执行核心逻辑

  const responseData = {
    code: 200,
    message: "请求成功",
    data: { id: 1, name: "商品 A", price: 99 },
  };

  console.log("📦 格式化响应数据:", JSON.stringify(responseData, null, 2));
});

📌 说明:

  • next() 之后,将数据包装成统一格式 { code, message, data }
  • 统一格式化数据,避免不同接口返回不同格式,增加前端解析负担
  • 适用于: RESTful API 规范,确保所有接口返回一致。

2. 统一错误处理 ❌

next() 之后,捕获所有中间件和核心逻辑中的异常,防止接口崩溃。

js 复制代码
app.use(async (next) => {
  try {
    await next();
  } catch (error) {
    console.error("🚨 捕获到错误:", error.message);
    console.log("⚠️ 统一返回错误响应 { code: 500, message: '服务器内部错误' }");
  }
});

📌 说明:

  • try-catch 捕获 next() 里的错误,避免接口直接崩溃。
  • 如果代码中抛出 throw new Error("XXX"),这个中间件可以拦截并统一返回 500 错误
  • 适用于: 防止 API 500 直接暴露内部错误信息

总结

  1. 这个简化版的 Koa 不到 10 行代码,但完全复现了 Koa 的中间件执行机制。
  2. 洋葱模型的执行方式,让我们可以在 next() 之前进行 前置逻辑 (如权限校验、日志记录),在 next() 之后进行 后置逻辑(如格式化响应、错误处理)。
  3. 通过 async/await 让代码 结构清晰,执行流可控 ,比传统的 callback 方式更易读、更易维护。

如果你还没有用过 Koa,希望这个小例子能帮助你理解它的 核心机制! 🚀

相关推荐
加减法原则22 分钟前
组件通信与设计模式
前端
冴羽25 分钟前
SvelteKit 最新中文文档教程(10)—— 部署 Cloudflare Pages 和 Cloudflare Workers
前端·javascript·svelte
fridayCodeFly28 分钟前
v-form标签里的:rules有什么作用。如何定义。
前端·javascript·vue.js
前端_学习之路37 分钟前
axios--源码解析
java·开发语言·javascript·ajax
xixixin_38 分钟前
【uniapp】内容瀑布流
java·前端·uni-app
计算机毕设定制辅导-无忧学长1 小时前
响应式 Web 设计:HTML 与 CSS 协同学习的进度(二)
前端·css·html
yzp01121 小时前
html方法收集
前端·javascript·html
paradoxaaa_1 小时前
VUE2导出el-table数据为excel并且按字段分多个sheet
javascript·vue.js·excel
lbh1 小时前
前端处理 .xlsx 文件流并触发下载指南
前端·javascript
xixixin_1 小时前
【uniapp】各端获取路由路径的方法
前端·javascript·uni-app·vue