中间件必须是返回Promise的async函数,接收ctx和next参数,通过await next()串联执行、透传状态、显式抛错;典型应用如日志统计需在前后记录时间与状态,并用try/catch捕获错误;推荐用工厂函数封装可配置中间件,确保ctx数据共享与错误穿透。

核心是让中间件函数返回 Promise,接收上下文(ctx)和 next 函数,并在合适位置 await next(),从而串联执行链、透传状态、捕获错误。
中间件必须满足的标准签名
一个可复用的异步中间件不是普通函数,它需严格遵循以下约定:
- 函数声明为 async,或显式返回 Promise
- 接收两个参数:ctx(承载请求/响应数据、用户信息、起始时间等的上下文对象)和 next(调用后续中间件的函数)
- 内部通过 await next() 主动移交控制权,等待下游完成后再继续本层逻辑
- 所有异常必须 显式抛出(如 throw err),不能静默吞掉,否则上层错误处理机制失效
典型功能封装:日志与耗时统计
这是最常用也最能体现中间件价值的场景。它不修改业务逻辑,只增强可观测性:
- 在 await next() 前记录请求开始时间与路径
- 在 await next() 后计算耗时,读取 ctx.status 等字段输出完整日志行
- 用 try/catch 包裹 await next(),捕获下游抛出的错误并统一打印 + 再次 throw
- 避免在 catch 块中直接 res.end() 或 ctx.body = ...,除非你明确要在此终结流程(此时它已不是“中间”件)
支持配置的工厂函数写法
硬编码的中间件复用性差。应把参数提取出来,返回一个中间件函数:
- 例如超时中间件:const timeout = (ms = 5000) => async (ctx, next) => { ... }
- 工厂函数本身不执行逻辑,只返回符合标准签名的中间件
- 这样就能灵活组合:app.use(timeout(3000)), app.use(logger), app.use(auth)
- 配置项还可包括错误码、兜底响应体、是否忽略特定路径等,提升实用性
错误传播与 ctx 数据透传
中间件的价值在于“管道式”协作,关键靠两点支撑:
- ctx 是唯一共享载体:前序中间件可设置 ctx.user、ctx.db、ctx.startTime,后续直接读取,无需层层传参
- 错误必须穿透:任意中间件 throw 的错误,会跳过剩余中间件,直达顶层错误处理器(如 Koa 的 onerror 或自定义 error handler)
- 不要在中间件里用 try/catch 吞掉错误后 return;若要做降级,应在 catch 中设置 ctx.status / ctx.body,再 throw 或 return,视设计意图而定

















