Skip to content

Context 上下文系统

Context 提供请求级别的全局状态管理,让你能在中间件之间优雅地共享数据。

🤔 为什么需要 Context?

想象这个场景:你有 3 个中间件,第 1 个获取了用户信息,第 3 个需要用这个信息。传统做法?

typescript
// ❌ 方案 1:全局变量(危险!多个请求会互相污染)
let currentUser = null

middleware1(() => {
  currentUser = getUser()
})

middleware3(() => {
  console.log(currentUser)  // 可能是别人的用户!
})

// ❌ 方案 2:层层传递(恶心!)
middleware1((input, next) => {
  const user = getUser()
  return next({ ...input, user })  // 传递下去
})

Context 的解决方案

typescript
import { createContext } from 'farrow-pipeline'

// 创建一个用户上下文
const UserContext = createContext<User | null>(null)

// 第 1 个中间件:设置用户
pipeline.use((req, next) => {
  const user = authenticate(req)
  UserContext.set(user)  // 🎯 设置!
  return next(req)
})

// 第 3 个中间件:获取用户
pipeline.use((req) => {
  const user = UserContext.get()  // 🎯 获取!
  return { user, data: processRequest(req) }
})

魔法原理:每次 pipeline.run() 都会创建独立的容器,不同请求互不干扰。并发安全!

Context 基本操作

创建 Context

typescript
import { createContext } from 'farrow-pipeline'

// 创建带默认值的 Context
const UserContext = createContext<User | null>(null)
const CounterContext = createContext<number>(0)
const ConfigContext = createContext({ debug: false })

// 泛型参数指定类型
const LoggerContext = createContext<{
  log: (msg: string) => void
  error: (msg: string) => void
}>({
  log: console.log,
  error: console.error
})

获取值

typescript
// 获取当前值(可能是默认值)
const user = UserContext.get()

// 获取并使用
pipeline.use((input) => {
  const user = UserContext.get()
  if (user) {
    console.log(`当前用户: ${user.name}`)
  }
  return input
})

设置值

typescript
// 设置新值
UserContext.set(newUser)

// 在中间件中设置
pipeline.use((input, next) => {
  const user = authenticate(input)
  UserContext.set(user)
  return next(input)
})

断言非空

typescript
// 断言值存在(如果是 null/undefined 会抛错)
const user = UserContext.assert()

// 使用场景
pipeline.use((input) => {
  // 假设前面的中间件已经设置了用户
  const user = UserContext.assert()  // 如果为 null,抛出异常
  return processForUser(user, input)
})

🎯 实战例子

例子 1: 多上下文协作

typescript
import { createContext, createPipeline } from 'farrow-pipeline'

type User = { id: string; name: string }
type Logger = { log: (msg: string) => void }

// 创建多个上下文
const UserContext = createContext<User | null>(null)
const RequestIdContext = createContext<string>('')
const LoggerContext = createContext<Logger>({ log: console.log })

const pipeline = createPipeline<Request, Response>()

// 中间件 1:初始化上下文
pipeline.use((req, next) => {
  UserContext.set(authenticate(req))
  RequestIdContext.set(generateId())
  LoggerContext.set(createLogger())
  return next(req)
})

// 中间件 2:使用上下文
pipeline.use((req) => {
  const user = UserContext.get()
  const requestId = RequestIdContext.get()
  const logger = LoggerContext.get()

  logger.log(`[${requestId}] 用户 ${user?.name} 发起请求`)

  return { user, requestId, data: '...' }
})

例子 2: 请求追踪

typescript
const TraceContext = createContext<{
  traceId: string
  startTime: number
  steps: string[]
}>({
  traceId: '',
  startTime: 0,
  steps: []
})

const tracePipeline = createPipeline<Request, Response>()

// 初始化追踪
tracePipeline.use((req, next) => {
  TraceContext.set({
    traceId: generateTraceId(),
    startTime: Date.now(),
    steps: []
  })
  return next(req)
})

// 记录步骤
function addStep(name: string) {
  const trace = TraceContext.get()
  trace.steps.push(`${name}: ${Date.now() - trace.startTime}ms`)
}

// 业务中间件
tracePipeline.use((req, next) => {
  addStep('认证开始')
  const user = authenticate(req)
  addStep('认证完成')
  return next(req)
})

tracePipeline.use((req) => {
  addStep('处理开始')
  const result = processRequest(req)
  addStep('处理完成')

  const trace = TraceContext.get()
  return {
    result,
    trace: {
      id: trace.traceId,
      duration: Date.now() - trace.startTime,
      steps: trace.steps
    }
  }
})

例子 3: 数据库连接管理

typescript
interface Database {
  query: (sql: string) => Promise<any>
  close: () => Promise<void>
}

const DBContext = createContext<Database | null>(null)

const dbPipeline = createAsyncPipeline<Request, Response>()

// 打开数据库连接
dbPipeline.use(async (req, next) => {
  const db = await openDatabase()
  DBContext.set(db)

  try {
    return await next(req)
  } finally {
    await db.close()  // 确保连接关闭
  }
})

// 使用数据库
dbPipeline.use(async (req) => {
  const db = DBContext.assert()  // 断言数据库已连接
  const users = await db.query('SELECT * FROM users')
  return { users }
})

🔒 上下文隔离

重要特性:每个 pipeline.run() 都有独立的容器!

typescript
const CounterContext = createContext(0)

const pipeline = createPipeline<string, string>()
  .use((input, next) => {
    const count = CounterContext.get()
    CounterContext.set(count + 1)
    return next(`${input}:${CounterContext.get()}`)
  })

// 并发执行,每个都从 0 开始计数
await Promise.all([
  pipeline.run('A'),  // "A:1"
  pipeline.run('B'),  // "B:1"(不是 "B:2"!)
  pipeline.run('C')   // "C:1"(不是 "C:3"!)
])

隔离演示

typescript
const UserContext = createContext<string>('匿名')

const pipeline = createPipeline<string, string>()

pipeline.use((input, next) => {
  UserContext.set(input)
  return next(input)
})

pipeline.use((input, next) => {
  // 模拟异步操作
  setTimeout(() => {
    console.log(`用户: ${UserContext.get()}`)
  }, 100)
  return next(input)
})

pipeline.use((input) => {
  return `处理完成: ${input}`
})

// 快速连续调用
pipeline.run('Alice')
pipeline.run('Bob')
pipeline.run('Charlie')

// 输出(顺序可能不同,但值是隔离的):
// 用户: Alice
// 用户: Bob
// 用户: Charlie

高级用法

自定义 Context Hook

typescript
// 创建自定义 Hook
function useUser() {
  const user = UserContext.get()
  if (!user) {
    throw new Error('用户未登录')
  }
  return user
}

// 使用
pipeline.use((req) => {
  const user = useUser()  // 自动断言用户存在
  return processForUser(user, req)
})

Context 组合

typescript
interface AppContext {
  user: User | null
  db: Database
  logger: Logger
}

// 创建组合上下文
const AppContexts = {
  user: createContext<User | null>(null),
  db: createContext<Database | null>(null),
  logger: createContext<Logger>(console)
}

// 初始化所有上下文
function initializeContext(user: User, db: Database, logger: Logger) {
  AppContexts.user.set(user)
  AppContexts.db.set(db)
  AppContexts.logger.set(logger)
}

// 使用
pipeline.use(async (req, next) => {
  const user = await authenticate(req)
  const db = await getDatabase()
  const logger = createLogger()

  initializeContext(user, db, logger)

  return next(req)
})

嵌套 Context

typescript
const OuterContext = createContext<string>('外层')
const InnerContext = createContext<string>('内层')

const outerPipeline = createPipeline<string, string>()
  .use((input, next) => {
    OuterContext.set('外层值')
    return next(input)
  })

const innerPipeline = createPipeline<string, string>()
  .use((input, next) => {
    InnerContext.set('内层值')
    console.log('外层:', OuterContext.get())  // 可以访问外层
    console.log('内层:', InnerContext.get())
    return next(input)
  })

// 嵌套调用
outerPipeline.use((input, next) => {
  const runInner = usePipeline(innerPipeline)
  return runInner(input)
})

最佳实践

✅ 适合用 Context

  • 🟢 跨中间件共享的请求级数据(用户、请求 ID)
  • 🟢 需要隔离的状态
  • 🟢 数据库连接、日志器等资源
typescript
// ✅ 好:适合用 Context
const UserContext = createContext<User | null>(null)
const RequestIdContext = createContext<string>('')
const DBContext = createContext<Database | null>(null)

❌ 不适合用 Context

  • 🔴 全局配置(用模块变量)
  • 🔴 可以通过参数传递的数据
  • 🔴 不需要隔离的状态
typescript
// ❌ 不必要:用模块变量就行
const ConfigContext = createContext({ apiUrl: 'https://api.example.com' })

// 改成这样:
const API_URL = 'https://api.example.com'

命名规范

typescript
// ✅ 好:以 Context 结尾
const UserContext = createContext<User | null>(null)
const LoggerContext = createContext<Logger>(console)

// ❌ 不好:名称不清晰
const User = createContext<User | null>(null)
const log = createContext<Logger>(console)

类型安全

typescript
// ✅ 好:明确指定类型
const UserContext = createContext<User | null>(null)

// ❌ 不好:依赖类型推导
const UserContext = createContext(null)  // 类型是 null

这是一个第三方 Farrow 文档站 | 用 ❤️ 和 TypeScript 构建