Lucas

问题

TypeScript 生态中的大多数弹性库都与 HTTP 客户端绑定(axios-retry、p-retry、Polly.js),或者需要将函数包装在带有 .execute() 仪式的类中。如果你只是想在不引入重度依赖的情况下,为 任意 异步函数——数据库查询、内部服务调用、文件操作——添加重试逻辑、超时和熔断器,该怎么办?

欢迎使用 houhou

什么是 houhou?

Houhou 是一个零依赖的 TypeScript 库(约 500 行代码),可为任意异步函数添加可组合的弹性策略。被包装的函数保留 完全相同的签名 —— 你可以像调用原始函数一样调用它。

import { task } from 'houhou'

const charge = task(chargeCard)
  .retry(3)
  .timeout(10_000)
  .fallback(() => ({ status: 'pending' }))

await charge(account, amount)

进入全屏模式 退出全屏模式

策略一览

重试

失败时重新执行,使用固定或指数退避:

task(fetchUser).retry(3) // 简写

task(fetchUser).retry({
  attempts: 5,
  backoff: 'exponential',
  jitter: true,
  delay: 500
})

进入全屏模式 退出全屏模式

超时

如果函数未在指定时间内完成,则拒绝:

task(fetchUser).timeout(5000)

进入全屏模式 退出全屏模式

降级

失败时运行替代函数:

task(fetchUser).fallback(() => loadFromCache(id))

进入全屏模式 退出全屏模式

熔断器

防止对不健康服务重复调用:

task(queryDb).circuitBreaker({
  failureThreshold: 5,
  successThreshold: 2,
  resetTimeout: 30_000
})

进入全屏模式 退出全屏模式

延迟

执行前等待:

task(syncData).delay(1000)

进入全屏模式 退出全屏模式

策略顺序很重要

策略是 嵌套 的:最后调用的方法包装前面的方法。执行顺序与声明顺序相反。

task(fn).retry(3).timeout(1000)
// → 超时包装重试
// → 函数运行 → 失败时重试(最多 3 次)→ 总超时 1 秒
// → 如果超时触发,则不再重试

进入全屏模式 退出全屏模式

task(fn).timeout(1000).retry(3)
// → 重试包装超时
// → 函数运行 → 1 秒超时 → 如果超时触发,重试会捕获它
// → 整个周期重复最多 3 次

进入全屏模式 退出全屏模式

类型安全 — 策略锁定

每个方法都是 可锁定的。TypeScript 在编译时防止重复配置同一策略,运行时的 Set 守卫也会强制执行:

const t = task(fn).retry(3)// @ts-expect-error — 'retry' 已被锁定
t.retry(2) // 运行时也会抛出异常

进入全屏模式 退出全屏模式

使用 AbortSignal 取消

Houhou 使用 AbortController 在超时时或提供外部信号时取消操作。AbortSignal 作为 最后一个参数 传递给你的函数:

const fn = (url: string, signal?: AbortSignal) => fetch(url, { signal })

task(fn).timeout(5000)('https://api.example.com')
// → 超时触发 → controller.abort() → fetch 被取消

进入全屏模式 退出全屏模式

你也可以传递自己的外部信号:

const controller = new AbortController()
const promise = task(fn).timeout(5000).retry(3)('url', controller.signal)

controller.abort() // 取消所有操作

进入全屏模式 退出全屏模式

组合示例

策略可以按任意顺序流畅地链式调用:

const resilient = task(callApi)
  .retry({ attempts: 3, backoff: 'exponential' })
  .timeout(5000)
  .fallback(loadFromCache)
  .circuitBreaker({ failureThreshold: 5, successThreshold: 2, resetTimeout: 30_000 })
  .delay(100)

进入全屏模式 退出全屏模式

名称由来

"houhou" 在日语中意为 "方法" 或 "做法" —— 非常适合描述 如何 执行你的函数的库。

立即尝试

npm install houhou
# 或
pnpm add houhou
yarn add houhou

进入全屏模式 退出全屏模式

GitHub: https://github.com/smokeeaasd/houhou
npm: https://npmjs.com/package/houhou