文档
CodeRabbit
Cloudflare
AG Grid
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
防抖器 API 参考
节流器 API 参考
速率限制器 API 参考
队列 API 参考
批处理器 API 参考
指南

异步速率限制指南

速率限制指南中的所有核心概念同样适用于异步速率限制。

何时使用异步速率限制

通常,您可以直接使用正常的同步速率限制器,它也可以与异步函数一起工作。但是,对于高级用例,例如想要使用速率限制函数的返回值(而不是仅仅调用一个setState副作用),或者将您的错误处理逻辑放在速率限制器中,您可以使用异步速率限制器。

TanStack Pacer 中的异步速率限制

TanStack Pacer 通过 AsyncRateLimiter 类和 asyncRateLimit 函数提供异步速率限制。

基本用法示例

以下是一个基本示例,展示了如何为 API 操作使用异步速率限制器

ts
const rateLimitedApi = asyncRateLimit(
  async (id: string) => {
    const response = await fetch(`/api/data/${id}`)
    return response.json()
  },
  {
    limit: 5,
    window: 1000,
    onExecute: (limiter) => {
      console.log('API call succeeded:', limiter.store.state.successCount)
    },
    onReject: (limiter) => {
      console.log(`Rate limit exceeded. Try again in ${limiter.getMsUntilNextWindow()}ms`)
    },
    onError: (error, limiter) => {
      console.error('API call failed:', error)
    }
  }
)

// Usage
try {
  const result = await rateLimitedApi('123')
  // Handle successful result
} catch (error) {
  // Handle errors if no onError handler was provided
  console.error('API call failed:', error)
}

注意:在使用 React 时,建议使用 useAsyncRateLimitedCallback hook,而不是 asyncRateLimit 函数,以便更好地与 React 的生命周期集成和自动清理。

与同步速率限制的主要区别

1. 返回值处理

与返回一个布尔值指示成功的同步速率限制器不同,异步版本允许您捕获和使用速率限制函数的返回值。 maybeExecute 方法返回一个 Promise,该 Promise 会使用函数的返回值解析,允许您等待结果并适当地处理它。

2. 错误处理

异步速率限制器提供强大的错误处理能力

  • 如果您的速率限制函数抛出错误并且没有提供 onError 处理程序,则该错误将被抛出并传播到调用者
  • 如果您提供了一个 onError 处理程序,错误将被捕获并传递到处理程序,而不是被抛出
  • 可以使用 throwOnError 选项来控制错误抛出行为
    • 当为 true 时(如果没有 onError 处理程序,则为默认值),将抛出错误
    • 当为 false 时(如果提供了 onError 处理程序,则为默认值),错误将被忽略
    • 可以显式设置以覆盖这些默认值
  • 您可以使用 limiter.store.state.errorCount 跟踪错误计数,并使用 limiter.store.state.isExecuting 检查执行状态
  • 速率限制器维护其状态,并且可以在发生错误后继续使用
  • 超出限制的限流拒绝(当达到限制时)通过 onReject 处理程序与执行错误分开处理

3. 不同的回调函数

AsyncRateLimiter 支持以下回调函数

  • onSuccess:在每次成功执行后调用,提供结果、执行的参数和速率限制器实例
  • onSettled:在每次执行后调用(成功或失败),提供执行的参数和速率限制器实例
  • onError:如果异步函数抛出错误,则调用,提供错误、导致错误的参数和速率限制器实例

异步和同步速率限制器都支持 onReject 回调函数,用于处理被阻止的执行。

示例

ts
const asyncLimiter = new AsyncRateLimiter(async (id) => {
  await saveToAPI(id)
}, {
  limit: 5,
  window: 1000,
  onExecute: (rateLimiter) => {
    // Called after each successful execution
    console.log('Async function executed', rateLimiter.store.state.successCount)
  },
  onReject: (rateLimiter) => {
    // Called when an execution is rejected
    console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`)
  },
  onError: (error) => {
    // Called if the async function throws an error
    console.error('Async function failed:', error)
  }
})

4. 顺序执行

由于速率限制器的 maybeExecute 方法返回一个 Promise,您可以选择在开始下一次执行之前等待每次执行。这使您可以控制执行顺序并确保每次调用处理最新的数据。这对于处理依赖于先前调用结果的操作或在保持数据一致性至关重要时特别有用。

例如,如果您正在更新用户的个人资料,然后立即获取其更新后的数据,您可以在开始获取之前等待更新操作完成。

高级功能:重试和中止支持

异步速率限制器通过与 AsyncRetryer 集成,包含内置的重试和中止功能。这些功能有助于处理瞬态故障并提供对正在进行的运营的控制。

重试支持

使用 asyncRetryerOptions 配置失败的速率限制函数执行的自动重试

ts
const rateLimitedApi = asyncRateLimit(
  async (userId: string) => {
    // This might fail due to network issues
    const data = await api.fetchUser(userId)
    return data
  },
  {
    limit: 5,
    window: 1000,
    asyncRetryerOptions: {
      maxAttempts: 3,
      backoff: 'exponential',
      baseWait: 1000,
      maxWait: 10000,
      jitter: 0.3
    }
  }
)

有关重试策略、退避算法、抖动和高级重试模式的完整文档,请参阅 异步重试指南

中止支持

使用中止功能取消正在进行的速率限制执行

ts
const rateLimiter = new AsyncRateLimiter(
  async (userId: string) => {
    // Access the abort signal for this execution
    const signal = rateLimiter.getAbortSignal()
    if (signal) {
      const response = await fetch(`/api/users/${userId}`, { signal })
      return response.json()
    }
  },
  { limit: 5, window: 1000 }
)

// Start some operations
rateLimiter.maybeExecute('user1')
rateLimiter.maybeExecute('user2')

// Later, abort any in-flight executions
rateLimiter.abort()

中止功能

  • 使用 AbortController 取消所有正在进行的速率限制执行
  • 不会清除执行时间或重置速率限制器
  • 可以与重试支持一起使用

有关中止模式和与 fetch/axios 集成的更多详细信息,请参阅 异步重试指南

在实例之间共享选项

使用 asyncRateLimiterOptions 在不同的 AsyncRateLimiter 实例之间共享常用选项

ts
import { asyncRateLimiterOptions, AsyncRateLimiter } from '@tanstack/pacer'

const sharedOptions = asyncRateLimiterOptions({
  limit: 5,
  window: 1000,
  onSuccess: (result, args, limiter) => console.log('Success')
})

const limiter1 = new AsyncRateLimiter(fn1, { ...sharedOptions, key: 'limiter1' })
const limiter2 = new AsyncRateLimiter(fn2, { ...sharedOptions, onError: (error) => console.error('Error') })

动态选项和启用/禁用

与同步速率限制器一样,异步速率限制器支持 limitwindowenabled 的动态选项,这些选项可以是接收速率限制器实例的函数。这允许进行复杂、运行时自适应的速率限制行为。

状态管理

AsyncRateLimiter 类使用 TanStack Store 进行反应式状态管理,提供对执行状态、错误跟踪和拒绝统计信息的实时访问。所有状态都存储在 TanStack Store 中,可以通过 asyncLimiter.store.state 访问,但是,如果您正在使用 React 或 Solid 等框架适配器,您将不想从这里读取状态。相反,您将从 asyncLimiter.state 读取状态,并提供一个选择器回调作为 useAsyncRateLimiter hook 的第三个参数,以选择加入状态跟踪,如下所示。

状态选择器(框架适配器)

框架适配器支持以两种方式订阅状态更改

1. 使用 asyncRateLimiter.Subscribe 组件(推荐用于组件树订阅)

使用 Subscribe 组件订阅组件树深处的状态更改,而无需将选择器传递给钩子。这对于希望在子组件中订阅状态非常有用。

tsx
// Default behavior - no reactive state subscriptions at hook level
const asyncRateLimiter = useAsyncRateLimiter(asyncFn, { limit: 5, window: 1000 })

// Subscribe to state changes deep in component tree using Subscribe component
<asyncRateLimiter.Subscribe selector={(state) => ({ isExecuting: state.isExecuting })}>
  {(state) => (
    <div>{state.isExecuting ? 'Executing...' : 'Idle'}</div>
  )}
</asyncRateLimiter.Subscribe>

2. 使用 selector 参数(用于钩子级别订阅)

selector 参数允许您指定哪些状态更改将触发钩子级别上的反应式更新,从而通过在发生不相关的状态更改时防止不必要的更新来优化性能。

默认情况下,asyncRateLimiter.state 为空({}),因为选择器默认情况下为空。 这是来自 TanStack Store useStore 的反应式状态存储的位置。您必须通过提供选择器函数来选择加入状态跟踪。

ts
// Default behavior - no reactive state subscriptions
const asyncLimiter = useAsyncRateLimiter(asyncFn, { limit: 5, window: 1000 })
console.log(asyncLimiter.state) // {}

// Opt-in to re-render when isExecuting changes
const asyncLimiter = useAsyncRateLimiter(
  asyncFn, 
  { limit: 5, window: 1000 },
  (state) => ({ isExecuting: state.isExecuting })
)
console.log(asyncLimiter.state.isExecuting) // Reactive value

// Multiple state properties
const asyncLimiter = useAsyncRateLimiter(
  asyncFn,
  { limit: 5, window: 1000 },
  (state) => ({
    isExecuting: state.isExecuting,
    successCount: state.successCount,
    errorCount: state.errorCount
  })
)

初始状态

您可以在创建异步速率限制器时提供初始状态值

ts
const savedState = localStorage.getItem('async-rate-limiter-state')
const initialState = savedState ? JSON.parse(savedState) : {}

const asyncLimiter = new AsyncRateLimiter(asyncFn, {
  limit: 5,
  window: 1000,
  initialState
})

订阅状态更改

Store 是响应式的并支持订阅

ts
const asyncLimiter = new AsyncRateLimiter(asyncFn, { limit: 5, window: 1000 })

// Subscribe to state changes
const unsubscribe = asyncLimiter.store.subscribe((state) => {
  // do something with the state like persist it to localStorage
})

// Unsubscribe when done
unsubscribe()

注意:当使用框架适配器时,这是不必要的,因为底层的 useStore hook 已经执行了此操作。您还可以导入并使用 useStore 来自 TanStack Store,以在任何必要时将 rateLimiter.store.state 转换为具有自定义选择器的反应式状态。

可用状态属性

AsyncRateLimiterState 包含

  • errorCount:导致错误的函数执行次数
  • executionTimes:速率限制计算的执行时间戳数组
  • isExecuting:速率限制函数是否正在异步执行
  • lastResult:最近一次成功函数执行的结果
  • maybeExecuteCount: maybeExecute 被调用的次数
  • rejectionCount:由于速率限制而被拒绝的函数执行次数
  • settledCount:已完成的函数执行次数(成功或出错)
  • status:当前执行状态('disabled' | 'exceeded' | 'idle')
  • successCount:成功完成的函数执行次数

辅助方法

异步速率限制器提供基于当前状态计算值的辅助方法

ts
const asyncLimiter = new AsyncRateLimiter(asyncFn, { limit: 5, window: 1000 })

// These methods use the current state to compute values
console.log(asyncLimiter.getRemainingInWindow()) // Number of calls remaining in current window
console.log(asyncLimiter.getMsUntilNextWindow()) // Milliseconds until next window

这些方法是计算值,不需要通过存储访问。

框架适配器

每个框架适配器都提供了钩子,这些钩子建立在核心异步速率限制功能之上,以集成到框架的状态管理系统。诸如 createAsyncRateLimiteruseAsyncRateLimitedCallback 或类似的钩子,每个框架都可用。


有关核心速率限制概念和同步速率限制,请参阅 速率限制指南