Skip to main content

Middleware 调度与执行

概述

Keq 的中间件执行器(Middleware Orchestrator)负责管理中间件的执行流程、执行状态以及请求上下文(Context)对象。 并且在本篇文章中,你还将了解到 KeqContext 的两个变种:KeqSharedContextKeqExecutionContext

Context 类型体系

类型关系

KeqContext (TypeScript Interface)
    ├── KeqSharedContext (Class implements KeqContext)
    └── KeqExecutionContext (Class implements KeqContext)

类型说明

类型说明
KeqContextTypeScript 接口,定义了 Context 对象 的核心属性(不包含 orchestration)。
KeqSharedContextKeqContext 的完整实现类,使用 gettersetter 保护数据,防止中间件意外修改。
KeqExecutionContextKeqSharedContext 基础上增加了 orchestration 属性,提供中间件执行状态的访问和控制能力。

context.orchestration 属性详解

KeqExecutionContextKeqSharedContext 的核心区别在于 orchestration 属性,它提供了对中间件执行流程的访问和控制能力。

API 接口

属性/方法类型描述
context.orchestration.middlewaresKeqMiddlewareContext[]中间件执行状态数组,记录所有中间件的执行情况
context.orchestration.fork()Function创建独立的中间件执行分支,用于并行或后台执行

context.orchestration.middlewares

提供了对所有中间件执行状态的实时访问,每个元素包含以下信息:

字段类型描述
namestring中间件名称
status'idle' | 'pending' | 'fulfilled' | 'rejected'当前执行状态
finishedboolean是否已执行完成

使用示例:打印中间件执行状态

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()
  })