【不到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,希望这个小例子能帮助你理解它的 核心机制! 🚀

相关推荐
独立开阀者_FwtCoder14 分钟前
8年磨一剑,Koa 3.0 正式发布!看看带来了哪些新功能
前端·javascript·后端
Frankabcdefgh20 分钟前
初中级前端面试全攻略:自我介绍模板、项目讲解套路与常见问答
前端·面试·职场和发展
2401_8784545322 分钟前
thymeleaf的使用和小结
前端·javascript·学习
brzhang30 分钟前
宝藏发现:Sim Studio,一款让AI工作流搭建变简单的开源利器
前端·后端·github
2301_7994049130 分钟前
AJAX 介绍
前端·ajax·axios
脚本语言_菜鸟31 分钟前
音频转base64
android·javascript·音视频
拖孩36 分钟前
【Nova UI】十三、打造组件库之按钮组件(中):样式雕琢全攻略
前端·javascript·vue.js
高峰君主1 小时前
WebAssembly全栈革命:在Rust与JavaScript之间构建高性能桥梁
javascript·rust·wasm
好_快1 小时前
Lodash源码阅读-sortedUniqBy
前端·javascript·源码阅读
小流苏生1 小时前
这只是一罐过期了七年的红牛……
前端·人工智能·程序员