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

使用异步速率限制器

函数: useAsyncRateLimiter()

ts
function useAsyncRateLimiter<TFn, TSelected>(
   fn, 
   options, 
selector): ReactAsyncRateLimiter<TFn, TSelected>
function useAsyncRateLimiter<TFn, TSelected>(
   fn, 
   options, 
selector): ReactAsyncRateLimiter<TFn, TSelected>

定义于: react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:178

一个低级别的 React hook,用于创建一个 AsyncRateLimiter 实例,以限制异步函数在一个时间窗口内可以执行的次数。

此 hook 设计灵活且独立于状态管理 - 它仅返回一个速率限制器实例,您可以将其集成到任何状态管理解决方案中(useState、Redux、Zustand、Jotai 等)。

速率限制允许异步函数在一个时间窗口内执行指定次数,然后阻止后续调用,直到窗口期过去。这对于遵守 API 速率限制、管理资源约束或控制异步操作的爆发非常有用。

与非异步的 RateLimiter 不同,此异步版本支持从速率受限函数返回。这使其非常适合 API 调用和其他异步操作,您希望在其中获取 maybeExecute 调用的结果,而不是在速率受限函数内部将结果设置到状态变量中。

速率限制器支持两种类型的窗口

  • 'fixed': 一个严格的窗口,在窗口期后重置。窗口内的所有执行都会计入限制,并且窗口会在期满后完全重置。
  • 'sliding': 一个滚动窗口,允许在旧的执行过期时进行新的执行。这提供了更一致的执行速率。

错误处理

  • 如果提供了 onError 处理程序,它将使用错误和速率限制器实例被调用
  • 如果 throwOnError 为 true(当没有提供 onError 处理程序时的默认值),错误将被抛出
  • 如果 throwOnError 为 false(当提供 onError 处理程序时的默认值),错误将被吞没
  • onError 和 throwOnError 可以一起使用 - 在任何错误被抛出之前,处理程序都会被调用
  • 可以使用底层的 AsyncRateLimiter 实例检查错误状态
  • 速率限制拒绝(当超出限制时)与执行错误分开处理,通过 onReject 处理程序。

状态管理和选择器

该 hook 使用 TanStack Store 进行响应式状态管理。 selector 参数允许您指定哪些状态更改将触发重新渲染,通过防止不相关的状态更改发生不必要的重新渲染来优化性能。

默认情况下,将没有响应式状态订阅,您必须通过提供 selector 函数来选择加入状态跟踪。这可以防止不必要的重新渲染,并让您完全控制组件何时更新。只有在您提供 selector 时,组件才会在选定的状态值发生更改时重新渲染。

可用的状态属性

  • errorCount:导致错误的函数执行次数。
  • executionTimes:执行发生的时间戳数组,用于速率限制计算。
  • isExecuting: 速率受限函数当前是否正在异步执行
  • lastResult:最近一次成功函数执行的结果。
  • rejectionCount:由于速率限制而被拒绝的函数执行次数。
  • settleCount:已完成(成功或错误)的函数执行次数
  • successCount:成功完成的函数执行次数。

类型参数

TFn extends AnyAsyncFunction

TSelected = {}

参数

fn

TFn

options

AsyncRateLimiterOptions<TFn>

选择器

(state) => TSelected

Returns (返回)

ReactAsyncRateLimiter<TFn, TSelected>

示例

tsx
// Default behavior - no reactive state subscriptions
const asyncRateLimiter = useAsyncRateLimiter(
  async (id: string) => {
    const data = await api.fetchData(id);
    return data; // Return value is preserved
  },
  { limit: 5, window: 1000 } // 5 calls per second
);

// Opt-in to re-render when execution state changes (optimized for loading indicators)
const asyncRateLimiter = useAsyncRateLimiter(
  async (id: string) => {
    const data = await api.fetchData(id);
    return data;
  },
  { limit: 5, window: 1000 },
  (state) => ({ isExecuting: state.isExecuting })
);

// Opt-in to re-render when results are available (optimized for data display)
const asyncRateLimiter = useAsyncRateLimiter(
  async (id: string) => {
    const data = await api.fetchData(id);
    return data;
  },
  { limit: 5, window: 1000 },
  (state) => ({
    lastResult: state.lastResult,
    successCount: state.successCount
  })
);

// Opt-in to re-render when error/rejection state changes (optimized for error handling)
const asyncRateLimiter = useAsyncRateLimiter(
  async (id: string) => {
    const data = await api.fetchData(id);
    return data;
  },
  {
    limit: 5,
    window: 1000,
    onError: (error) => console.error('API call failed:', error),
    onReject: (rateLimiter) => console.log('Rate limit exceeded')
  },
  (state) => ({
    errorCount: state.errorCount,
    rejectionCount: state.rejectionCount
  })
);

// Opt-in to re-render when execution metrics change (optimized for stats display)
const asyncRateLimiter = useAsyncRateLimiter(
  async (id: string) => {
    const data = await api.fetchData(id);
    return data;
  },
  { limit: 5, window: 1000 },
  (state) => ({
    successCount: state.successCount,
    errorCount: state.errorCount,
    settleCount: state.settleCount,
    rejectionCount: state.rejectionCount
  })
);

// Opt-in to re-render when execution times change (optimized for window calculations)
const asyncRateLimiter = useAsyncRateLimiter(
  async (id: string) => {
    const data = await api.fetchData(id);
    return data;
  },
  { limit: 5, window: 1000 },
  (state) => ({ executionTimes: state.executionTimes })
);

// With state management and return value
const [data, setData] = useState(null);
const { maybeExecute, state } = useAsyncRateLimiter(
  async (query) => {
    const result = await searchAPI(query);
    setData(result);
    return result; // Return value can be used by the caller
  },
  {
    limit: 10,
    window: 60000, // 10 calls per minute
    onReject: (rateLimiter) => {
      console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`);
    },
    onError: (error) => {
      console.error('API call failed:', error);
    }
  }
);

// Access the selected state (will be empty object {} unless selector provided)
const { isExecuting, lastResult, rejectionCount } = state;
// Default behavior - no reactive state subscriptions
const asyncRateLimiter = useAsyncRateLimiter(
  async (id: string) => {
    const data = await api.fetchData(id);
    return data; // Return value is preserved
  },
  { limit: 5, window: 1000 } // 5 calls per second
);

// Opt-in to re-render when execution state changes (optimized for loading indicators)
const asyncRateLimiter = useAsyncRateLimiter(
  async (id: string) => {
    const data = await api.fetchData(id);
    return data;
  },
  { limit: 5, window: 1000 },
  (state) => ({ isExecuting: state.isExecuting })
);

// Opt-in to re-render when results are available (optimized for data display)
const asyncRateLimiter = useAsyncRateLimiter(
  async (id: string) => {
    const data = await api.fetchData(id);
    return data;
  },
  { limit: 5, window: 1000 },
  (state) => ({
    lastResult: state.lastResult,
    successCount: state.successCount
  })
);

// Opt-in to re-render when error/rejection state changes (optimized for error handling)
const asyncRateLimiter = useAsyncRateLimiter(
  async (id: string) => {
    const data = await api.fetchData(id);
    return data;
  },
  {
    limit: 5,
    window: 1000,
    onError: (error) => console.error('API call failed:', error),
    onReject: (rateLimiter) => console.log('Rate limit exceeded')
  },
  (state) => ({
    errorCount: state.errorCount,
    rejectionCount: state.rejectionCount
  })
);

// Opt-in to re-render when execution metrics change (optimized for stats display)
const asyncRateLimiter = useAsyncRateLimiter(
  async (id: string) => {
    const data = await api.fetchData(id);
    return data;
  },
  { limit: 5, window: 1000 },
  (state) => ({
    successCount: state.successCount,
    errorCount: state.errorCount,
    settleCount: state.settleCount,
    rejectionCount: state.rejectionCount
  })
);

// Opt-in to re-render when execution times change (optimized for window calculations)
const asyncRateLimiter = useAsyncRateLimiter(
  async (id: string) => {
    const data = await api.fetchData(id);
    return data;
  },
  { limit: 5, window: 1000 },
  (state) => ({ executionTimes: state.executionTimes })
);

// With state management and return value
const [data, setData] = useState(null);
const { maybeExecute, state } = useAsyncRateLimiter(
  async (query) => {
    const result = await searchAPI(query);
    setData(result);
    return result; // Return value can be used by the caller
  },
  {
    limit: 10,
    window: 60000, // 10 calls per minute
    onReject: (rateLimiter) => {
      console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`);
    },
    onError: (error) => {
      console.error('API call failed:', error);
    }
  }
);

// Access the selected state (will be empty object {} unless selector provided)
const { isExecuting, lastResult, rejectionCount } = state;
我们的合作伙伴
Code Rabbit
Unkey
订阅 Bytes

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

Bytes

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

订阅 Bytes

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

Bytes

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