文档
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 通过 AsyncDebouncer 类和 asyncDebounce 函数提供异步防抖。

基本用法示例

以下是一个基本示例,展示了如何使用异步防抖器进行搜索操作

ts
const debouncedSearch = asyncDebounce(
  async (searchTerm: string) => {
    const results = await fetchSearchResults(searchTerm)
    return results
  },
  {
    wait: 500,
    onSuccess: (results, args, debouncer) => {
      console.log('Search succeeded:', results)
      console.log('Search arguments:', args)
    },
      onError: (error, args, debouncer) => {
    console.error('Search failed:', error)
    console.log('Failed arguments:', args)
  }
  }
)

// Usage
try {
  const results = await debouncedSearch('query')
  // Handle successful results
} catch (error) {
  // Handle errors if no onError handler was provided
  console.error('Search failed:', error)
}

注意:在使用 React 时,为了更好地与 React 的生命周期集成和自动清理,请优先使用 useAsyncDebouncedCallback 钩子,而不是 asyncDebounce 函数。

与同步防抖的主要区别

1. 返回值处理

与返回 void 的同步防抖器不同,异步版本允许你捕获和使用防抖函数的返回值。 maybeExecute 方法返回一个 Promise,该 Promise 会使用函数的返回值解析,允许你等待结果并适当地处理它。

2. 错误处理

异步防抖器提供强大的错误处理能力

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

3. 不同的回调函数

AsyncDebouncer 支持以下回调

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

示例

ts
const asyncDebouncer = new AsyncDebouncer(async (value) => {
  await saveToAPI(value)
}, {
  wait: 500,
  onSuccess: (result, args, debouncer) => {
    // Called after each successful execution
    console.log('Async function executed', debouncer.store.state.successCount)
    console.log('Executed arguments:', args)
  },
  onSettled: (args, debouncer) => {
    // Called after each execution attempt
    console.log('Async function settled', debouncer.store.state.settleCount)
    console.log('Settled arguments:', args)
  },
  onError: (error) => {
    // Called if the async function throws an error
    console.error('Async function failed:', error)
  }
})

4. 顺序执行

由于防抖器的 maybeExecute 方法返回一个 Promise,你可以选择在开始下一次执行之前等待每次执行。这使你能够控制执行顺序并确保每次调用处理最新的数据。这对于处理依赖于先前调用结果或在维护数据一致性至关重要的操作尤其有用。

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

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

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

重试支持

使用 asyncRetryerOptions 配置失败的防抖函数执行的自动重试

ts
const debouncedSave = asyncDebounce(
  async (data: string) => {
    // This might fail due to network issues
    await api.save(data)
  },
  {
    wait: 500,
    asyncRetryerOptions: {
      maxAttempts: 3,
      backoff: 'exponential',
      baseWait: 1000,
      maxWait: 10000,
      jitter: 0.3
    }
  }
)

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

中止支持

使用中止功能取消正在进行的防抖执行

ts
const debouncer = new AsyncDebouncer(
  async (searchTerm: string) => {
    // Access the abort signal for this execution
    const signal = debouncer.getAbortSignal()
    if (signal) {
      const response = await fetch(`/api/search?q=${searchTerm}`, { signal })
      return response.json()
    }
  },
  { wait: 300 }
)

// Start a search
debouncer.maybeExecute('query')

// Later, abort any in-flight execution
debouncer.abort()

中止功能

  • 使用 AbortController 取消所有正在进行的防抖执行
  • 不会取消尚未开始的待处理执行(使用 cancel() 取消这些执行)
  • 可以与重试支持一起使用

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

在实例之间共享选项

使用 asyncDebouncerOptions 在不同的 AsyncDebouncer 实例之间共享常用选项

ts
import { asyncDebouncerOptions, AsyncDebouncer } from '@tanstack/pacer'

const sharedOptions = asyncDebouncerOptions({
  wait: 500,
  leading: false,
  trailing: true,
  onSuccess: (result, args, debouncer) => console.log('Success')
})

const debouncer1 = new AsyncDebouncer(fn1, { ...sharedOptions, key: 'debouncer1' })
const debouncer2 = new AsyncDebouncer(fn2, { ...sharedOptions, onError: (error) => console.error('Error') })

动态选项和启用/禁用

与同步防抖器一样,异步防抖器支持 waitenabled 的动态选项,这些选项可以是接收防抖器实例的函数。这允许进行复杂且适应性强的运行时防抖行为。

刷新待定执行

异步防抖器支持刷新待处理的执行,以立即触发它们

ts
const asyncDebouncer = new AsyncDebouncer(asyncFn, { wait: 1000 })

asyncDebouncer.maybeExecute('some-arg')
console.log(asyncDebouncer.store.state.isPending) // true

// Flush immediately instead of waiting
asyncDebouncer.flush()
console.log(asyncDebouncer.store.state.isPending) // false

状态管理

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

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

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

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

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

tsx
// Default behavior - no reactive state subscriptions at hook level
const asyncDebouncer = useAsyncDebouncer(asyncFn, { wait: 500 })

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

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

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

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

ts
// Default behavior - no reactive state subscriptions
const asyncDebouncer = useAsyncDebouncer(asyncFn, { wait: 500 })
console.log(asyncDebouncer.state) // {}

// Opt-in to re-render when isExecuting changes
const asyncDebouncer = useAsyncDebouncer(
  asyncFn, 
  { wait: 500 },
  (state) => ({ isExecuting: state.isExecuting })
)
console.log(asyncDebouncer.state.isExecuting) // Reactive value

// Multiple state properties
const asyncDebouncer = useAsyncDebouncer(
  asyncFn,
  { wait: 500 },
  (state) => ({
    isExecuting: state.isExecuting,
    successCount: state.successCount,
    errorCount: state.errorCount
  })
)

初始状态

你可以在创建异步防抖器时提供初始状态值。这通常用于从持久存储恢复状态

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

const asyncDebouncer = new AsyncDebouncer(asyncFn, {
  wait: 500,
  initialState
})

订阅状态更改

Store 是响应式的并支持订阅

ts
const asyncDebouncer = new AsyncDebouncer(asyncFn, { wait: 500 })

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

// Unsubscribe when done
unsubscribe()

注意:在使用框架适配器时,这是不必要的,因为底层的 useStore 钩子已经执行了此操作。你还可以导入并使用 useStore 来自 TanStack Store,以将 debouncer.store.state 转换为带有自定义选择器的反应式状态,只要需要即可。

可用状态属性

AsyncDebouncerState 包含

  • canLeadingExecute:防抖器是否可以在超时的前沿执行
  • errorCount:导致错误的函数执行次数
  • isExecuting:防抖函数是否当前正在异步执行
  • isPending: 防抖器是否正在等待超时触发执行
  • lastArgs: 上次调用 maybeExecute 的参数
  • lastResult:最近一次成功函数执行的结果
  • maybeExecuteCount: maybeExecute 被调用的次数
  • settleCount:已完成的函数执行次数(成功或出错)
  • status:当前执行状态('disabled' | 'idle' | 'pending' | 'executing' | 'settled')
  • successCount:成功完成的函数执行次数

框架适配器

每个框架适配器都提供构建在核心异步防抖功能之上的钩子,以与框架的状态管理系统集成。每个框架都提供类似 createAsyncDebounceruseAsyncDebouncedCallback 或类似的钩子。


有关核心防抖概念和同步防抖,请参阅《防抖指南》。