框架
版本
防抖器 API 参考
节流器 API 参考
速率限制器 API 参考
队列 API 参考
批处理器 API 参考

速率限制指南

速率限制、节流和防抖是控制函数执行频率的三个不同方法。每种技术都有不同的阻止执行方式,这使得它们“有损”——这意味着某些函数调用在请求过于频繁时不会执行。了解何时使用每种方法对于构建高性能和可靠的应用程序至关重要。本指南将介绍 TanStack Pacer 的速率限制概念。

注意

TanStack Pacer 目前仅是一个前端库。这些是用于客户端速率限制的实用工具。

速率限制概念

速率限制是一种技术,它限制函数在特定时间窗口内执行的速率。它特别适用于您希望防止函数被过于频繁调用的场景,例如在处理 API 请求或其他外部服务调用时。它是最原始的方法,因为它允许执行以突发方式发生,直到达到配额。

速率限制可视化

text
Rate Limiting (limit: 3 calls per window)
Timeline: [1 second per tick]
                                        Window 1                  |    Window 2            
Calls:        ⬇️     ⬇️     ⬇️     ⬇️     ⬇️                             ⬇️     ⬇️
Executed:     ✅     ✅     ✅     ❌     ❌                             ✅     ✅
             [=== 3 allowed ===][=== blocked until window ends ===][=== new window =======]
Rate Limiting (limit: 3 calls per window)
Timeline: [1 second per tick]
                                        Window 1                  |    Window 2            
Calls:        ⬇️     ⬇️     ⬇️     ⬇️     ⬇️                             ⬇️     ⬇️
Executed:     ✅     ✅     ✅     ❌     ❌                             ✅     ✅
             [=== 3 allowed ===][=== blocked until window ends ===][=== new window =======]

窗口类型

TanStack Pacer 支持两种类型的速率限制窗口:

  1. 固定窗口(默认)

    • 一个严格的窗口,在窗口期过后重置
    • 窗口内的所有执行都计入限制
    • 窗口将在周期后完全重置
    • 可能导致窗口边界处的突发行为
  2. 滑动窗口

    • 一个滚动窗口,允许在旧的执行到期时进行新的执行
    • 随时间推移提供更一致的执行速率
    • 更适合保持稳定的执行流
    • 防止窗口边界处的突发行为

这是滑动窗口速率限制的可视化图

text
Sliding Window Rate Limiting (limit: 3 calls per window)
Timeline: [1 second per tick]
                                        Window 1                  |    Window 2            
Calls:        ⬇️     ⬇️     ⬇️     ⬇️     ⬇️                             ⬇️     ⬇️
Executed:     ✅     ✅     ✅     ❌     ✅                             ✅     ✅
             [=== 3 allowed ===][=== oldest expires, new allowed ===][=== continues sliding =======]
Sliding Window Rate Limiting (limit: 3 calls per window)
Timeline: [1 second per tick]
                                        Window 1                  |    Window 2            
Calls:        ⬇️     ⬇️     ⬇️     ⬇️     ⬇️                             ⬇️     ⬇️
Executed:     ✅     ✅     ✅     ❌     ✅                             ✅     ✅
             [=== 3 allowed ===][=== oldest expires, new allowed ===][=== continues sliding =======]

关键区别在于,使用滑动窗口时,一旦最旧的执行到期,就会允许新的执行。与固定窗口方法相比,这产生了更一致的执行流。

何时使用速率限制

速率限制在处理前端操作时尤为重要,这些操作可能会意外地压垮后端服务或导致浏览器性能问题。

何时不使用速率限制

速率限制是控制函数执行频率最原始的方法。它是三种技术中最不灵活、限制性最强的一种。考虑使用节流防抖来获得更间隔的执行。

提示

您很可能不希望在大多数用例中使用“速率限制”。考虑改用节流防抖

速率限制的“有损”性质也意味着某些执行将被拒绝和丢失。如果您需要确保所有执行始终成功,这可能会成为一个问题。如果您需要确保所有执行都排队等待执行,但具有节流的延迟以减慢执行速率,则可以考虑使用队列

TanStack Pacer 中的速率限制

TanStack Pacer 同时提供同步和异步速率限制。本指南涵盖同步 RateLimiter 类和 rateLimit 函数。有关异步速率限制,请参阅异步速率限制指南

使用 rateLimit 进行基本用法

rateLimit 函数是将速率限制添加到任何函数的最简单方法。它非常适合大多数只需要执行简单限制的用例。

ts
import { rateLimit } from '@tanstack/pacer'

// Rate limit API calls to 5 per minute
const rateLimitedApi = rateLimit(
  (id: string) => fetchUserData(id),
  {
    limit: 5,
    window: 60 * 1000, // 1 minute in milliseconds
    windowType: 'fixed', // default
    onReject: (rateLimiter) => {
      console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`)
    }
  }
)

// First 5 calls will execute immediately
rateLimitedApi('user-1') // ✅ Executes
rateLimitedApi('user-2') // ✅ Executes
rateLimitedApi('user-3') // ✅ Executes
rateLimitedApi('user-4') // ✅ Executes
rateLimitedApi('user-5') // ✅ Executes
rateLimitedApi('user-6') // ❌ Rejected until window resets
import { rateLimit } from '@tanstack/pacer'

// Rate limit API calls to 5 per minute
const rateLimitedApi = rateLimit(
  (id: string) => fetchUserData(id),
  {
    limit: 5,
    window: 60 * 1000, // 1 minute in milliseconds
    windowType: 'fixed', // default
    onReject: (rateLimiter) => {
      console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`)
    }
  }
)

// First 5 calls will execute immediately
rateLimitedApi('user-1') // ✅ Executes
rateLimitedApi('user-2') // ✅ Executes
rateLimitedApi('user-3') // ✅ Executes
rateLimitedApi('user-4') // ✅ Executes
rateLimitedApi('user-5') // ✅ Executes
rateLimitedApi('user-6') // ❌ Rejected until window resets

注意: 在使用 React 时,请优先使用 useRateLimitedCallback hook 而不是 rateLimit 函数,以更好地与 React 的生命周期和自动清理集成。

使用 RateLimiter 类进行高级用法

对于需要对速率限制行为进行额外控制的更复杂场景,您可以直接使用 RateLimiter 类。这使您可以访问其他方法和状态信息。

ts
import { RateLimiter } from '@tanstack/pacer'

// Create a rate limiter instance
const limiter = new RateLimiter(
  (id: string) => fetchUserData(id),
  {
    limit: 5,
    window: 60 * 1000,
    onExecute: (rateLimiter) => {
      console.log('Function executed', rateLimiter.store.state.executionCount)
    },
    onReject: (rateLimiter) => {
      console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`)
    }
  }
)

// Access current state via TanStack Store
console.log(limiter.getRemainingInWindow()) // Number of calls remaining in current window
console.log(limiter.store.state.executionCount) // Total number of successful executions
console.log(limiter.store.state.rejectionCount) // Total number of rejected executions

// Attempt to execute (returns boolean indicating success)
limiter.maybeExecute('user-1')

// Update options dynamically
limiter.setOptions({ limit: 10 }) // Increase the limit

// Reset all counters and state
limiter.reset()
import { RateLimiter } from '@tanstack/pacer'

// Create a rate limiter instance
const limiter = new RateLimiter(
  (id: string) => fetchUserData(id),
  {
    limit: 5,
    window: 60 * 1000,
    onExecute: (rateLimiter) => {
      console.log('Function executed', rateLimiter.store.state.executionCount)
    },
    onReject: (rateLimiter) => {
      console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`)
    }
  }
)

// Access current state via TanStack Store
console.log(limiter.getRemainingInWindow()) // Number of calls remaining in current window
console.log(limiter.store.state.executionCount) // Total number of successful executions
console.log(limiter.store.state.rejectionCount) // Total number of rejected executions

// Attempt to execute (returns boolean indicating success)
limiter.maybeExecute('user-1')

// Update options dynamically
limiter.setOptions({ limit: 10 }) // Increase the limit

// Reset all counters and state
limiter.reset()

启用/禁用

RateLimiter 类支持通过 enabled 选项进行启用/禁用。使用 setOptions 方法,您可以随时启用/禁用速率限制器。

注意

enabled 选项用于启用/禁用实际的函数执行。禁用速率限制器并不会关闭速率限制,它只会阻止函数被执行。

ts
const limiter = new RateLimiter(fn, { 
  limit: 5, 
  window: 1000,
  enabled: false // Disable by default
})
limiter.setOptions({ enabled: true }) // Enable at any time
const limiter = new RateLimiter(fn, { 
  limit: 5, 
  window: 1000,
  enabled: false // Disable by default
})
limiter.setOptions({ enabled: true }) // Enable at any time

enabled 选项也可以是一个返回布尔值的函数,允许根据运行时条件动态启用/禁用

ts
const limiter = new RateLimiter(fn, {
  limit: 5,
  window: 1000,
  enabled: (limiter) => {
    return limiter.store.state.executionCount < 100 // Disable after 100 executions
  }
})
const limiter = new RateLimiter(fn, {
  limit: 5,
  window: 1000,
  enabled: (limiter) => {
    return limiter.store.state.executionCount < 100 // Disable after 100 executions
  }
})

如果您使用的是框架适配器,其中速率限制器选项是响应式的,您可以将 enabled 选项设置为条件值,以便动态启用/禁用速率限制器。但是,如果您直接使用 rateLimit 函数或 RateLimiter 类,则必须使用 setOptions 方法来更改 enabled 选项,因为传递的选项实际上是传递给了 RateLimiter 类的构造函数。

动态选项

RateLimiter 中的几个选项支持通过接收速率限制器实例的回调函数来动态设置值。

ts
const limiter = new RateLimiter(fn, {
  // Dynamic limit based on execution count
  limit: (limiter) => {
    return Math.max(1, 10 - limiter.store.state.executionCount) // Decrease limit with each execution
  },
  // Dynamic window based on execution count
  window: (limiter) => {
    return limiter.store.state.executionCount * 1000 // Increase window with each execution
  },
  // Dynamic enabled state based on execution count
  enabled: (limiter) => {
    return limiter.store.state.executionCount < 100 // Disable after 100 executions
  }
})
const limiter = new RateLimiter(fn, {
  // Dynamic limit based on execution count
  limit: (limiter) => {
    return Math.max(1, 10 - limiter.store.state.executionCount) // Decrease limit with each execution
  },
  // Dynamic window based on execution count
  window: (limiter) => {
    return limiter.store.state.executionCount * 1000 // Increase window with each execution
  },
  // Dynamic enabled state based on execution count
  enabled: (limiter) => {
    return limiter.store.state.executionCount < 100 // Disable after 100 executions
  }
})

以下选项支持动态值

  • enabled: 可以是布尔值或返回布尔值的函数
  • limit:可以是数字或返回数字的函数。
  • window:可以是数字或返回数字的函数。

这允许实现复杂且能适应运行时条件的速率限制行为。

回调选项

同步 RateLimiter 支持以下回调:

ts
const limiter = new RateLimiter(fn, {
  limit: 5,
  window: 1000,
  onExecute: (rateLimiter) => {
    // Called after each successful execution
    console.log('Function executed', rateLimiter.store.state.executionCount)
  },
  onReject: (rateLimiter) => {
    // Called when an execution is rejected
    console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`)
  }
})
const limiter = new RateLimiter(fn, {
  limit: 5,
  window: 1000,
  onExecute: (rateLimiter) => {
    // Called after each successful execution
    console.log('Function executed', rateLimiter.store.state.executionCount)
  },
  onReject: (rateLimiter) => {
    // Called when an execution is rejected
    console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`)
  }
})

onExecute 回调在速率限制函数成功执行后被调用,而 onReject 回调在执行因速率限制而被拒绝时被调用。这些回调对于跟踪执行、更新 UI 状态或向用户提供反馈非常有用。

状态管理

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

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

框架适配器支持 selector 参数,该参数允许您指定哪些状态更改将触发重新渲染。这通过防止不相关的状态更改发生不必要的重新渲染来优化性能。

默认情况下,rateLimiter.state 是空的({}),因为选择器默认为空。 这是来自 TanStack Store 的响应式状态 useStore 存储的地方。您必须提供一个选择器函数来选择加入状态跟踪。

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

// Opt-in to re-render when rejectionCount changes
const limiter = useRateLimiter(
  fn, 
  { limit: 5, window: 1000 },
  (state) => ({ rejectionCount: state.rejectionCount })
)
console.log(limiter.state.rejectionCount) // Reactive value

// Multiple state properties
const limiter = useRateLimiter(
  fn,
  { limit: 5, window: 1000 },
  (state) => ({
    executionCount: state.executionCount,
    rejectionCount: state.rejectionCount,
    status: state.status
  })
)
// Default behavior - no reactive state subscriptions
const limiter = useRateLimiter(fn, { limit: 5, window: 1000 })
console.log(limiter.state) // {}

// Opt-in to re-render when rejectionCount changes
const limiter = useRateLimiter(
  fn, 
  { limit: 5, window: 1000 },
  (state) => ({ rejectionCount: state.rejectionCount })
)
console.log(limiter.state.rejectionCount) // Reactive value

// Multiple state properties
const limiter = useRateLimiter(
  fn,
  { limit: 5, window: 1000 },
  (state) => ({
    executionCount: state.executionCount,
    rejectionCount: state.rejectionCount,
    status: state.status
  })
)

初始状态

创建速率限制器时,您可以提供初始状态值。这通常用于从持久存储中恢复状态。

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

const limiter = new RateLimiter(fn, {
  limit: 5,
  window: 1000,
  initialState
})
// Load initial state from localStorage
const savedState = localStorage.getItem('rate-limiter-state')
const initialState = savedState ? JSON.parse(savedState) : {}

const limiter = new RateLimiter(fn, {
  limit: 5,
  window: 1000,
  initialState
})

订阅状态更改

Store 是响应式的并支持订阅

ts
const limiter = new RateLimiter(fn, { limit: 5, window: 1000 })

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

// Unsubscribe when done
unsubscribe()
const limiter = new RateLimiter(fn, { limit: 5, window: 1000 })

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

// Unsubscribe when done
unsubscribe()

注意: 使用框架适配器时,这没有必要,因为底层的 useStore hook 已经完成了这个工作。您也可以根据需要从 TanStack Store 导入和使用 useStore,将 rateLimiter.store.state 转换为具有自定义选择器的响应式状态。

可用状态属性

RateLimiterState 包括:

  • executionCount: 已完成的函数执行次数
  • executionTimes:执行发生的时间戳数组,用于速率限制计算。
  • isExceeded:速率限制器是否已超出限制。
  • maybeExecuteCount: 调用 maybeExecute 的次数
  • rejectionCount:由于速率限制而被拒绝的函数执行次数。
  • status:当前执行状态(“disabled” | “exceeded” | “idle”)

辅助方法

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

ts
const limiter = new RateLimiter(fn, { limit: 5, window: 1000 })

// These methods use the current state to compute values
console.log(limiter.getRemainingInWindow()) // Number of calls remaining in current window
console.log(limiter.getMsUntilNextWindow()) // Milliseconds until next window
const limiter = new RateLimiter(fn, { limit: 5, window: 1000 })

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

这些方法是使用当前状态计算出的值,不需要通过 store 访问。

框架适配器

每个框架适配器都在速率限制器类周围构建了方便的 hooks 和函数。像 useRateLimitercreateRateLimiter 这样的 hooks 是小的包装器,可以减少在您的代码中为一些常见用例所需的样板代码。


有关异步速率限制(例如,API 调用、异步操作),请参阅异步速率限制指南

我们的合作伙伴
Code Rabbit
Unkey
订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。

订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。