本文目录导读:

- 核心原则:“Next” 函数
- Express.js 风格(顺序执行,基于回调)
- Koa.js 风格(洋葱模型,基于 async/await)
- 自定义 Pipeline(手写控制)
- 特殊场景:并行/条件性执行
- 总结:如何控制顺序?
通过函数的调用链和参数传递机制,显式或隐式地决定下一个中间件何时执行。
不同的中间件架构(如 Express/Koa/自定义Pipeline)控制方式略有不同,但原理相通,下面从最常见的场景和底层原理来详细说明。
核心原则:“Next” 函数
几乎所有的中间件系统(如 Express、Koa、Redux、Webpack Loader)都使用一个 next 函数来控制流程。
- 调用
next():表示“当前中间件处理完毕,将控制权交给下一个中间件”。 - 不调用
next():表示“请求到此为止”,后续中间件不会执行。 - 调用
next(error):表示“发生错误,跳转到错误处理中间件”。 - 调用
next('route')(某些框架特有):表示“跳过当前路由的剩余中间件,直接匹配下一个路由”。
Express.js 风格(顺序执行,基于回调)
在 Express 中,中间件按它们在代码中出现的顺序被添加到请求处理链中。
const express = require('express');
const app = express();
// 中间件 1:日志记录(先执行)
app.use((req, res, next) => {
console.log('1. 日志记录中间件');
// 关键:必须调用 next(),否则卡住
next();
});
// 中间件 2:请求解析(后执行)
app.use((req, res, next) => {
console.log('2. 解析请求体');
req.customData = { timestamp: Date.now() };
next();
});
// 中间件 3:路由处理
app.get('/data', (req, res) => {
console.log('3. 处理路由');
res.send(`Timestamp: ${req.customData.timestamp}`);
});
// 错误处理中间件(特殊,4个参数)
app.use((err, req, res, next) => {
console.error('错误中间件:', err.message);
res.status(500).send('服务器错误');
});
app.listen(3000);
控制要点:
- 代码顺序决定优先级:
app.use(middleware1)在前,middleware1先执行。 next()位置决定流程:可以在next()之前做前置处理(如日志),在next()之后做后置处理(如统一添加响应头)。
Koa.js 风格(洋葱模型,基于 async/await)
Koa 使用 async 函数和 await next() 实现“洋葱模型”,中间件执行顺序呈现先入后出的效果。
const Koa = require('koa');
const app = new Koa();
// 中间件 1:外层日志
app.use(async (ctx, next) => {
console.log('1. 进入中间件1');
await next(); // 进入下一个中间件,并挂起当前中间件
console.log('5. 退出中间件1');
});
// 中间件 2:内层日志 + 耗时操作
app.use(async (ctx, next) => {
console.log('2. 进入中间件2');
await new Promise(resolve => setTimeout(resolve, 100)); // 模拟异步
await next();
console.log('4. 退出中间件2');
});
// 中间件 3:响应处理
app.use(async (ctx) => {
console.log('3. 处理响应');
ctx.body = 'Hello Koa';
});
app.listen(3000);
// 控制台输出:1 -> 2 -> (等待100ms) -> 3 -> 4 -> 5
控制要点:
await next()之前的代码是“上游”处理(请求到达时执行)。await next()之后的代码是“下游”处理(响应返回时执行)。- 利用这个模型,可以轻松实现请求计时、权限校验(前置)、响应格式统一(后置)等。
自定义 Pipeline(手写控制)
如果不依赖框架,可以通过一个数组 + 索引来手动控制顺序。
class MiddlewarePipeline {
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn);
}
// 核心执行函数
execute(req, res) {
let index = 0;
const next = () => {
if (index < this.middlewares.length) {
const middleware = this.middlewares[index++];
// 将 req, res, next 传给中间件
middleware(req, res, next);
}
};
next(); // 启动链
}
}
// 使用示例
const pipeline = new MiddlewarePipeline();
pipeline.use((req, res, next) => {
console.log('A: 开始');
req.data = {};
next(); // 必须手动调用
console.log('A: 结束');
});
pipeline.use((req, res, next) => {
console.log('B: 处理中');
req.data.time = Date.now();
next();
});
pipeline.use((req, res, next) => {
console.log('C: 最终处理');
res.send(`Time: ${req.data.time}`);
});
// 模拟请求
pipeline.execute({ method: 'GET' }, { send: console.log });
// 输出:A: 开始 -> B: 处理中 -> C: 最终处理 -> A: 结束
控制要点:
- 通过维护一个递增的
index,确保中间件按注册顺序依次执行。 next()函数内部调用下一个中间件,形成链式调用。
特殊场景:并行/条件性执行
有时需要更灵活的控制,跳过”或“并行”执行中间件。
1 条件跳过
// 只有特定条件下才执行中间件
app.use((req, res, next) => {
if (req.query.bypass) {
// 跳过当前中间件,直接进入下一个
return next();
}
console.log('执行验证逻辑...');
next();
});
2 并行中间件(手动控制)
在 Express 原生不支持并行中间件,但可以通过 Promise.all 手动实现:
app.use(async (req, res, next) => {
const result = await Promise.all([
fetch('http://service1'),
fetch('http://service2')
]);
req.service1Data = result[0];
req.service2Data = result[1];
next(); // 等两个请求都完成后,再进入下一个中间件
});
如何控制顺序?
| 控制方式 | 对应代码 | 效果 |
|---|---|---|
| 注册顺序 | app.use(A); app.use(B); |
A 总是在 B 之前执行 |
next() 调用时机 |
next() 提前 vs 延后 |
决定上下游处理逻辑 |
| 条件判断 | if(条件) return next() |
跳过或选择中间件 |
| 错误传递 | next(error) |
跳转到错误处理链 |
| 异步等待 | await next() (Koa) |
挂起当前中间件,等内层执行完再继续 |
记住:next 函数是控制流的总开关,只要理解了“谁调用了 next,谁就交出控制权”,就能精确控制任何中间件系统的执行顺序。