中间件
题目描述
实现一个“简化版” Express 风格中间件系统,支持:
- 链式串行执行中间件
(req, next) - 错误处理中间件
(err, req, next) - 支持同步 / Promise / async 函数形式(通过手动调用
next驱动) - 通过
next(error?)进行错误切换
当前实现是一个最小可工作的责任链,与真实 Express / Koa 仍有差异:
- 不支持真正的“洋葱模型”(
next()之后的代码不会等待后续异步完成) - 没有
res/ 响应对象概念,所有状态附加在req上 - 不支持返回值控制、也没有全局最终回调 / 完成通知
- 不支持
next('route')、不支持路由匹配、挂载路径、正则、参数注入等
核心知识点
1. 责任链 + 函数参数长度分派
- 普通中间件:
function(req, next)→length === 2 - 错误中间件:
function(err, req, next)→length === 3 use()内根据func.length插入不同数组:cbHandlers/errHandlers- 没有其它类型判定(多余参数、默认参数会影响
length需谨慎)
2. 执行驱动机制(start(req))
内部维护两个独立索引:
| 变量 | 作用 |
|---|---|
idx | 当前正常中间件序号 |
errIdx | 当前错误中间件序号 |
调度函数 next(nextErr):
javascript
if (nextErr) {
func = errHandlers[errIdx++] // 进入错误处理链
args = [nextErr, req, next]
} else {
func = cbHandlers[idx++] // 进入正常链
args = [req, next]
}当某个错误中间件调用 next()(无错误)后,会继续回到正常链的“下一个”位置(不会回退 / 重试)。
3. 异步支持的边界
- 通过
Promise.resolve(func(...args)).catch(error => next(error))包裹执行 - 但:
start()/next()不返回 Promise,因此:- 无法
await app.start(req)得知执行完成时间 - 中间件里写:js
someAsync().then(() => next()) console.log('after')after会立即执行,不具备“洋葱式 after 钩子”效果
- 无法
- 想要“前后包裹(before/after)”语义需对框架进行 Promise 链或栈式封装改造
4. 错误传播语义
| 场景 | 结果 |
|---|---|
| 正常中间件抛同步异常 | 被 try/catch 捕获 → next(error) → 进入错误链 |
| 正常中间件返回 rejected Promise | .catch 捕获 → next(error) |
| 错误中间件抛异常 / reject | 再次进入下一个错误中间件(递进 errIdx) |
错误中间件调用 next()(无参数) | 回到正常链剩余部分 |
错误中间件不调用 next | 链终止(静默结束) |
没有“最终未处理错误回调”;若所有错误中间件都调用 next(err) 且耗尽,将静默结束(无 throw)。
5. 中止与防御性
- 访问越界:
func为undefined→ 判空后不再递归 → 链结束 - 重复调用
next():索引继续递增,可能“跳过”后续或造成逻辑混乱(无防重入保护) next()并不防止多次错误传播(可能层层进入 errHandlers 直到耗尽)
6. 典型用法模式
javascript
const app = new Middleware()
app.use((req, next) => {
req.log = ['start']
next()
})
app.use(async (req, next) => {
await new Promise(r => setTimeout(r, 10))
req.async = true
next()
})
app.use((err, req, next) => { // 错误中间件
req.errorHandled = err.message
next() // 继续后续正常链
})
app.use((req, next) => {
req.done = true
next()
})
app.start({ url: '/demo' })7. 与 Express / Koa 的关键差异
| 能力 | 当前实现 | Express | Koa |
|---|---|---|---|
| after(洋葱模型) | 不支持 | 通过 next() 回调顺序 + 同步栈局部支持 | await next() 天然支持 |
res/上下文 | 仅 req | req/res 双对象 | ctx 统一封装 |
| 路径匹配 | 不支持 | 支持 | 通过中间件可实现 |
| 返回 Promise | 否 | 否(部分中间件可自行封装) | 是(async 语义) |
| 多重 next 防护 | 无 | 很少(依赖约定) | 部分生态插件处理 |
| 未处理错误监控 | 静默结束 | 可挂接 error 事件 | 默认抛出,可捕获 |
代码实现
javascript
export default class Middleware {
cbHandlers = []
errHandlers = []
use(func) {
if (func.length === 2)
this.cbHandlers.push(func)
if (func.length === 3)
this.errHandlers.push(func)
}
start(req) {
let idx = 0
let errIdx = 0
const that = this
function next(nextErr) {
const args = [req, next]
let func = null
if (nextErr) {
func = that.errHandlers[errIdx++]
args.unshift(nextErr)
}
else {
func = that.cbHandlers[idx++]
}
try {
func && Promise.resolve(func(...args)).catch(error => next(error))
}
catch (error) {
next(error)
}
}
next()
}
}使用示例
javascript
import Middleware from './middleware'
const app = new Middleware()
// 普通中间件
app.use((req, next) => {
req.timeline = ['init']
next()
})
// 异步中间件
app.use(async (req, next) => {
await new Promise(r => setTimeout(r, 20))
req.timeline.push('async-done')
next()
})
// 触发错误
app.use((req, next) => {
if (!req.ok) {
return next(new Error('Not OK'))
}
next()
})
// 错误处理中间件
app.use((err, req, next) => {
req.error = err.message
// 继续执行后续正常中间件
next()
})
// 收尾中间件
app.use((req, next) => {
req.timeline.push('end')
console.log(req)
next()
})
app.start({ url: '/demo', ok: false })