框架
版本
Debouncer API 参考
Throttler API 参考
速率限制器 API 参考
队列 API 参考
批处理器 API 参考
批处理器示例

速率限制指南

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

Note

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

速率限制概念

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

速率限制可视化

text
速率限制 (限制: 每个窗口 3 次调用)
时间轴: [每个滴答 1 秒]
                                        窗口 1                  |    窗口 2
调用:        ⬇️     ⬇️     ⬇️     ⬇️     ⬇️                             ⬇️     ⬇️
已执行:     ✅     ✅     ✅     ❌     ❌                             ✅     ✅
             [=== 允许 3 次 ===][=== 阻塞直到窗口结束 ===][=== 新窗口 =======]
速率限制 (限制: 每个窗口 3 次调用)
时间轴: [每个滴答 1 秒]
                                        窗口 1                  |    窗口 2
调用:        ⬇️     ⬇️     ⬇️     ⬇️     ⬇️                             ⬇️     ⬇️
已执行:     ✅     ✅     ✅     ❌     ❌                             ✅     ✅
             [=== 允许 3 次 ===][=== 阻塞直到窗口结束 ===][=== 新窗口 =======]

窗口类型

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

  1. 固定窗口 (默认)

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

    • 随着旧执行过期而允许执行的滚动窗口
    • 提供更一致的执行速率
    • 更适合保持稳定的执行流程
    • 防止窗口边界出现突发行为

以下是滑动窗口速率限制的可视化:

text
滑动窗口速率限制 (限制: 每个窗口 3 次调用)
时间轴: [每个滴答 1 秒]
                                        窗口 1                  |    窗口 2
调用:        ⬇️     ⬇️     ⬇️     ⬇️     ⬇️                             ⬇️     ⬇️
已执行:     ✅     ✅     ✅     ❌     ✅                             ✅     ✅
             [=== 允许 3 次 ===][=== 最旧的过期,允许新的 ===][=== 继续滑动 =======]
滑动窗口速率限制 (限制: 每个窗口 3 次调用)
时间轴: [每个滴答 1 秒]
                                        窗口 1                  |    窗口 2
调用:        ⬇️     ⬇️     ⬇️     ⬇️     ⬇️                             ⬇️     ⬇️
已执行:     ✅     ✅     ✅     ❌     ✅                             ✅     ✅
             [=== 允许 3 次 ===][=== 最旧的过期,允许新的 ===][=== 继续滑动 =======]

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

何时使用速率限制

在处理可能意外地使后端服务不堪重负或导致浏览器性能问题的前端操作时,速率限制尤其重要。

何时不使用速率限制

速率限制是控制函数执行频率的最朴素的方法。它是三种技术中最不灵活和最具限制性的。考虑改用节流防抖以实现更分散的执行。

Tip

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

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

TanStack Pacer 中的速率限制

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

rateLimit 的基本用法

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

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

// 将 API 调用速率限制为每分钟 5 次
const rateLimitedApi = rateLimit(
  (id: string) => fetchUserData(id),
  {
    limit: 5,
    window: 60 * 1000, // 1 分钟(毫秒)
    windowType: 'fixed', // 默认
    onReject: (rateLimiter) => {
      console.log(`已超出速率限制。请在 ${rateLimiter.getMsUntilNextWindow()} 毫秒后重试`)
    }
  }
)

// 前 5 次调用将立即执行
rateLimitedApi('user-1') // ✅ 执行
rateLimitedApi('user-2') // ✅ 执行
rateLimitedApi('user-3') // ✅ 执行
rateLimitedApi('user-4') // ✅ 执行
rateLimitedApi('user-5') // ✅ 执行
rateLimitedApi('user-6') // ❌ 在窗口重置前被拒绝
import { rateLimit } from '@tanstack/pacer'

// 将 API 调用速率限制为每分钟 5 次
const rateLimitedApi = rateLimit(
  (id: string) => fetchUserData(id),
  {
    limit: 5,
    window: 60 * 1000, // 1 分钟(毫秒)
    windowType: 'fixed', // 默认
    onReject: (rateLimiter) => {
      console.log(`已超出速率限制。请在 ${rateLimiter.getMsUntilNextWindow()} 毫秒后重试`)
    }
  }
)

// 前 5 次调用将立即执行
rateLimitedApi('user-1') // ✅ 执行
rateLimitedApi('user-2') // ✅ 执行
rateLimitedApi('user-3') // ✅ 执行
rateLimitedApi('user-4') // ✅ 执行
rateLimitedApi('user-5') // ✅ 执行
rateLimitedApi('user-6') // ❌ 在窗口重置前被拒绝

RateLimiter 类的高级用法

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

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

// 创建一个速率限制器实例
const limiter = new RateLimiter(
  (id: string) => fetchUserData(id),
  {
    limit: 5,
    window: 60 * 1000,
    onExecute: (rateLimiter) => {
      console.log('函数已执行', rateLimiter.getExecutionCount())
    },
    onReject: (rateLimiter) => {
      console.log(`已超出速率限制。请在 ${rateLimiter.getMsUntilNextWindow()} 毫秒后重试`)
    }
  }
)

// 获取有关当前状态的信息
console.log(limiter.getRemainingInWindow()) // 当前窗口中剩余的调用次数
console.log(limiter.getExecutionCount()) // 成功执行的总次数
console.log(limiter.getRejectionCount()) // 被拒绝执行的总次数

// 尝试执行(返回布尔值指示成功)
limiter.maybeExecute('user-1')

// 动态更新选项
limiter.setOptions({ limit: 10 }) // 增加限制

// 重置所有计数器和状态
limiter.reset()
import { RateLimiter } from '@tanstack/pacer'

// 创建一个速率限制器实例
const limiter = new RateLimiter(
  (id: string) => fetchUserData(id),
  {
    limit: 5,
    window: 60 * 1000,
    onExecute: (rateLimiter) => {
      console.log('函数已执行', rateLimiter.getExecutionCount())
    },
    onReject: (rateLimiter) => {
      console.log(`已超出速率限制。请在 ${rateLimiter.getMsUntilNextWindow()} 毫秒后重试`)
    }
  }
)

// 获取有关当前状态的信息
console.log(limiter.getRemainingInWindow()) // 当前窗口中剩余的调用次数
console.log(limiter.getExecutionCount()) // 成功执行的总次数
console.log(limiter.getRejectionCount()) // 被拒绝执行的总次数

// 尝试执行(返回布尔值指示成功)
limiter.maybeExecute('user-1')

// 动态更新选项
limiter.setOptions({ limit: 10 }) // 增加限制

// 重置所有计数器和状态
limiter.reset()

启用/禁用

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

Note

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

ts
const limiter = new RateLimiter(fn, { 
  limit: 5, 
  window: 1000,
  enabled: false // 默认禁用
})
limiter.setOptions({ enabled: true }) // 随时启用
const limiter = new RateLimiter(fn, { 
  limit: 5, 
  window: 1000,
  enabled: false // 默认禁用
})
limiter.setOptions({ enabled: true }) // 随时启用

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

ts
const limiter = new RateLimiter(fn, {
  limit: 5,
  window: 1000,
  enabled: (limiter) => {
    return limiter.getExecutionCount() < 100 // 执行 100 次后禁用
  }
})
const limiter = new RateLimiter(fn, {
  limit: 5,
  window: 1000,
  enabled: (limiter) => {
    return limiter.getExecutionCount() < 100 // 执行 100 次后禁用
  }
})

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

动态选项

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

ts
const limiter = new RateLimiter(fn, {
  // 根据执行计数动态调整限制
  limit: (limiter) => {
    return Math.max(1, 10 - limiter.getExecutionCount()) // 每次执行减少限制
  },
  // 根据执行计数动态调整窗口
  window: (limiter) => {
    return limiter.getExecutionCount() * 1000 // 每次执行增加窗口
  },
  // 根据执行计数动态调整启用状态
  enabled: (limiter) => {
    return limiter.getExecutionCount() < 100 // 执行 100 次后禁用
  }
})
const limiter = new RateLimiter(fn, {
  // 根据执行计数动态调整限制
  limit: (limiter) => {
    return Math.max(1, 10 - limiter.getExecutionCount()) // 每次执行减少限制
  },
  // 根据执行计数动态调整窗口
  window: (limiter) => {
    return limiter.getExecutionCount() * 1000 // 每次执行增加窗口
  },
  // 根据执行计数动态调整启用状态
  enabled: (limiter) => {
    return limiter.getExecutionCount() < 100 // 执行 100 次后禁用
  }
})

以下选项支持动态值:

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

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

回调选项

同步 RateLimiter 支持以下回调:

ts
const limiter = new RateLimiter(fn, {
  limit: 5,
  window: 1000,
  onExecute: (rateLimiter) => {
    // 每次成功执行后调用
    console.log('函数已执行', rateLimiter.getExecutionCount())
  },
  onReject: (rateLimiter) => {
    // 当执行被拒绝时调用
    console.log(`已超出速率限制。请在 ${rateLimiter.getMsUntilNextWindow()} 毫秒后重试`)
  }
})
const limiter = new RateLimiter(fn, {
  limit: 5,
  window: 1000,
  onExecute: (rateLimiter) => {
    // 每次成功执行后调用
    console.log('函数已执行', rateLimiter.getExecutionCount())
  },
  onReject: (rateLimiter) => {
    // 当执行被拒绝时调用
    console.log(`已超出速率限制。请在 ${rateLimiter.getMsUntilNextWindow()} 毫秒后重试`)
  }
})

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


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