Middleware 调度与执行
概述
Keq 的中间件执行器(Middleware Orchestrator)负责管理中间件的执行流程、执行状态以及请求上下文(Context)对象。
并且在本篇文章中,你还将了解到 KeqContext 的两个变种:KeqSharedContext 和 KeqExecutionContext。
Context 类型体系
类型关系
KeqContext (TypeScript Interface)
├── KeqSharedContext (Class implements KeqContext)
└── KeqExecutionContext (Class implements KeqContext)类型说明
| 类型 | 说明 |
|---|---|
KeqContext | TypeScript 接口,定义了 Context 对象 的核心属性(不包含 orchestration)。 |
KeqSharedContext | KeqContext 的完整实现类,使用 getter 和 setter 保护数据,防止中间件意外修改。 |
KeqExecutionContext | 在 KeqSharedContext 基础上增加了 orchestration 属性,提供中间件执行状态的访问和控制能力。 |
context.orchestration 属性详解
KeqExecutionContext 与 KeqSharedContext 的核心区别在于 orchestration 属性,它提供了对中间件执行流程的访问和控制能力。
API 接口
| 属性/方法 | 类型 | 描述 |
|---|---|---|
context.orchestration.middlewares | KeqMiddlewareContext[] | 中间件执行状态数组,记录所有中间件的执行情况 |
context.orchestration.fork() | Function | 创建独立的中间件执行分支,用于并行或后台执行 |
context.orchestration.middlewares
提供了对所有中间件执行状态的实时访问,每个元素包含以下信息:
| 字段 | 类型 | 描述 |
|---|---|---|
name | string | 中间件名称 |
status | 'idle' | 'pending' | 'fulfilled' | 'rejected' | 当前执行状态 |
finished | boolean | 是否已执行完成 |
使用示例:打印中间件执行状态
import { KeqMiddleware, KeqExecutionContext } from 'keq'
const inspectMiddleware: KeqMiddleware = async (context: KeqExecutionContext, next) => {
console.log('执行前的中间件状态:')
context.orchestration.middlewares.forEach(mw => {
console.log(` ${mw.name}: ${mw.status}`)
})
await next()
console.log('执行后的中间件状态:')
context.orchestration.middlewares.forEach(mw => {
console.log(` ${mw.name}: ${mw.status} (已完成: ${mw.finished})`)
})
}context.orchestration.fork()
创建一个全新的中间件执行器,包含:
- 深度克隆:完整复制 context(request、options、data 等)
- 独立执行:拥有独立的中间件链和执行状态
- 互不影响:分支执行不会影响原始请求
实战示例:实现 SWR 缓存策略
SWR(Stale-While-Revalidate)是一种缓存策略:优先返回缓存数据,同时在后台更新缓存。
import { KeqMiddleware, KeqExecutionContext } from 'keq'
// 简单的内存缓存示例(生产环境需考虑缓存过期和内存管理)
const cache = new Map<string, Response>()
const swrCacheMiddleware: KeqMiddleware = async (context: KeqExecutionContext, next) => {
const cacheKey = context.request.url.href
const cachedResponse = cache.get(cacheKey)
if (cachedResponse) {
// 立即返回缓存数据
context.res = cachedResponse.clone()
// 在后台更新缓存
const forkedExecutor = context.orchestration.fork()
setTimeout(async () => {
try {
await forkedExecutor.execute()
if (forkedExecutor.context.response) {
cache.set(cacheKey, forkedExecutor.context.response)
}
} catch (error) {
console.error('后台更新缓存失败:', error)
}
}, 0)
return // 直接返回,跳过后续中间件
}
// 无缓存时正常执行
await next()
// 首次请求:保存到缓存
if (context.response) {
cache.set(cacheKey, context.response)
}
}注意事项
- 分叉的执行会触发后续完整的中间件链,包括所有副作用(如重试、日志记录等)
fork()也复制了当前的执行状态。你不可能执行已经执行过的中间件。
何必纠结
你总是应该优先选择 KeqContext
对于 99% 的使用场景,KeqContext 已经足够,它包含了所有常用属性:
import { request, KeqContext, KeqNext } from 'keq'
async function logging (context: KeqContext, next: KeqNext) => {
console.log('请求 URL:', context.request.url)
console.log('请求选项:', context.options)
await next()
console.log('响应状态:', context.response?.status)
}
request.use(logging)仅在必要时使用 KeqExecutionContext
某些事件回调只提供 KeqSharedContext(等价于KeqContext),所以使用 KeqContext 总是安全的,使用 KeqExecutionContext 最好先阅读文档:
import { request } from 'keq'
import type { KeqContext } from 'keq'
request
.get('/api/users')
.on('retry', ({ context }: { context: KeqContext }) => {
console.log('正在重试...', context.request.url)
console.log(context.orchestration.middlewares) // ❌ 错误:retry 事件中的 context 不是 KeqExecutionContext
})
.on('error', ({ context, error }: { context: KeqContext; error: Error }) => {
console.error('请求失败:', error.message)
console.error('请求 URL:', context.request.url)
console.log(context.orchestration.middlewares) // ❌ 错误:error 事件中的 context 不是 KeqExecutionContext
})
.use(async (context, next) => {
console.log(context.orchestration.middlewares) // ✅ 正确
await next()
})