Koa 最重要的,不是 ctx.body,而是它的 middleware onion model。
你可以把一次请求想成一艘飞船穿过多层巡防关卡:
请求进入
│
▼
关卡 A:先执行前半段
│
▼
关卡 B:先执行前半段
│
▼
关卡 C:先执行前半段
│
▼
业务核心舱
│
▼
关卡 C:再执行后半段
│
▼
关卡 B:再执行后半段
│
▼
关卡 A:再执行后半段
│
▼
响应离开这就是为什么 Koa 的中间件写法几乎总长这样:
app.use(async (ctx, next) => {
console.log('关卡 A 进入')
await next()
console.log('关卡 A 返回')
})
app.use(async (ctx, next) => {
console.log('关卡 B 进入')
await next()
console.log('关卡 B 返回')
})
app.use(async (ctx) => {
console.log('核心舱处理')
ctx.body = { ok: true }
})实际顺序是:
关卡 A 进入
关卡 B 进入
核心舱处理
关卡 B 返回
关卡 A 返回这和很多人脑中的“一个中间件跑完,再到下一个”不一样。
Koa 不是排队过栅栏,它更像:
先一层层往里钻,再一层层往外收。
这件事的工程价值非常大:
- 统计耗时很自然
- 统一日志很自然
- 统一错误处理很自然
- 统一响应包装很自然
- 对下游结果再加工也很自然
比如最经典的响应时间中间件:
app.use(async (ctx, next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
ctx.set('X-Response-Time', `${ms}ms`)
})这个写法之所以成立,就是因为:
先记开始时间
│
▼
让请求继续深入
│
▼
等最里面处理完返回
│
▼
再补上耗时信息如果你没把这一张图记住,你就永远只是“会写 Koa”,不会“真正理解 Koa”。