速率限制 (Rate Limiting)、節流 (Throttling) 與防抖 (Debouncing) 是三種控制函數執行頻率的不同方法。每種技術都以不同的方式阻擋執行,使其成為「有損」的 — 意味著當函數被要求過於頻繁執行時,某些呼叫將不會被執行。了解何時使用每種方法對於構建高效能且可靠的應用程式至關重要。本指南將涵蓋 TanStack Pacer 的防抖概念。
防抖是一種技術,它會延遲函數的執行,直到指定的不活動時間過去。與速率限制 (允許在限制內爆發性執行) 或節流 (確保執行間隔均勻) 不同,防抖會將多次快速函數呼叫合併為單一執行,且僅在呼叫停止後才會發生。這使得防抖非常適合處理事件爆發的情況,當你只關心活動結束後的最終狀態時。
防抖 (等待: 3 個刻度)
時間軸: [每秒 1 個刻度]
呼叫: ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
已執行: ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ⏳ -> ✅ ❌ ⏳ -> ✅
[=================================================================]
^ 在此處執行
無呼叫 3 個刻度後
[呼叫爆發] [更多呼叫] [等待] [新爆發]
無執行 重置計時器 [延遲執行] [等待] [延遲執行]
防抖 (等待: 3 個刻度)
時間軸: [每秒 1 個刻度]
呼叫: ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
已執行: ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ⏳ -> ✅ ❌ ⏳ -> ✅
[=================================================================]
^ 在此處執行
無呼叫 3 個刻度後
[呼叫爆發] [更多呼叫] [等待] [新爆發]
無執行 重置計時器 [延遲執行] [等待] [延遲執行]
當你想在採取行動前等待活動「暫停」時,防抖特別有效。這使其非常適合處理使用者輸入或其他快速觸發的事件,當你只關心最終狀態時。
常見使用情境包括:
防抖可能不是最佳選擇的情況:
TanStack Pacer 分別透過 Debouncer 和 AsyncDebouncer 類別 (及其對應的 debounce 和 asyncDebounce 函數) 提供同步和非同步防抖。
debounce 函數是為任何函數添加防抖的最簡單方法:
import { debounce } from '@tanstack/pacer'
// 防抖搜尋輸入以等待使用者停止輸入
const debouncedSearch = debounce(
(searchTerm: string) => performSearch(searchTerm),
{
wait: 500, // 最後一次按鍵後等待 500ms
}
)
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value)
})
import { debounce } from '@tanstack/pacer'
// 防抖搜尋輸入以等待使用者停止輸入
const debouncedSearch = debounce(
(searchTerm: string) => performSearch(searchTerm),
{
wait: 500, // 最後一次按鍵後等待 500ms
}
)
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value)
})
為了更精確控制防抖行為,你可以直接使用 Debouncer 類別:
import { Debouncer } from '@tanstack/pacer'
const searchDebouncer = new Debouncer(
(searchTerm: string) => performSearch(searchTerm),
{ wait: 500 }
)
// 獲取當前狀態資訊
console.log(searchDebouncer.getExecutionCount()) // 成功執行次數
console.log(searchDebouncer.getIsPending()) // 是否有呼叫待處理
// 動態更新選項
searchDebouncer.setOptions({ wait: 1000 }) // 增加等待時間
// 取消待處理的執行
searchDebouncer.cancel()
import { Debouncer } from '@tanstack/pacer'
const searchDebouncer = new Debouncer(
(searchTerm: string) => performSearch(searchTerm),
{ wait: 500 }
)
// 獲取當前狀態資訊
console.log(searchDebouncer.getExecutionCount()) // 成功執行次數
console.log(searchDebouncer.getIsPending()) // 是否有呼叫待處理
// 動態更新選項
searchDebouncer.setOptions({ wait: 1000 }) // 增加等待時間
// 取消待處理的執行
searchDebouncer.cancel()
同步防抖器支援前緣和後緣執行:
const debouncedFn = debounce(fn, {
wait: 500,
leading: true, // 第一次呼叫時立即執行
trailing: true, // 等待期後執行
})
const debouncedFn = debounce(fn, {
wait: 500,
leading: true, // 第一次呼叫時立即執行
trailing: true, // 等待期後執行
})
常見模式:
TanStack Pacer 防抖器特意不像其他防抖函式庫那樣提供 maxWait 選項。如果你需要讓執行在更分散的時間段內運行,請考慮改用 節流 技術。
Debouncer 類別支援透過 enabled 選項啟用/停用。使用 setOptions 方法,你可以隨時啟用/停用防抖器:
const debouncer = new Debouncer(fn, { wait: 500, enabled: false }) // 預設停用
debouncer.setOptions({ enabled: true }) // 隨時啟用
const debouncer = new Debouncer(fn, { wait: 500, enabled: false }) // 預設停用
debouncer.setOptions({ enabled: true }) // 隨時啟用
如果你使用的是框架適配器,其中防抖器選項是響應式的,你可以將 enabled 選項設置為條件值以動態啟用/停用防抖器:
// React 範例
const debouncer = useDebouncer(
setSearch,
{ wait: 500, enabled: searchInput.value.length > 3 } // 根據輸入長度啟用/停用 IF 使用支援響應式選項的框架適配器
)
// React 範例
const debouncer = useDebouncer(
setSearch,
{ wait: 500, enabled: searchInput.value.length > 3 } // 根據輸入長度啟用/停用 IF 使用支援響應式選項的框架適配器
)
然而,如果你使用的是 debounce 函數或直接使用 Debouncer 類別,則必須使用 setOptions 方法來更改 enabled 選項,因為傳遞的選項實際上會傳遞給 Debouncer 類別的建構函數。
// Solid 範例
const debouncer = new Debouncer(fn, { wait: 500, enabled: false }) // 預設停用
createEffect(() => {
debouncer.setOptions({ enabled: search().length > 3 }) // 根據輸入長度啟用/停用
})
// Solid 範例
const debouncer = new Debouncer(fn, { wait: 500, enabled: false }) // 預設停用
createEffect(() => {
debouncer.setOptions({ enabled: search().length > 3 }) // 根據輸入長度啟用/停用
})
同步和非同步防抖器都支援回呼選項,以處理防抖生命週期的不同方面:
同步 Debouncer 支援以下回呼:
const debouncer = new Debouncer(fn, {
wait: 500,
onExecute: (debouncer) => {
// 每次成功執行後呼叫
console.log('函數已執行', debouncer.getExecutionCount())
}
})
const debouncer = new Debouncer(fn, {
wait: 500,
onExecute: (debouncer) => {
// 每次成功執行後呼叫
console.log('函數已執行', debouncer.getExecutionCount())
}
})
onExecute 回呼在防抖函數每次成功執行後呼叫,這對於追蹤執行、更新 UI 狀態或執行清理操作非常有用。
非同步 AsyncDebouncer 的回呼集與同步版本不同。
const asyncDebouncer = new AsyncDebouncer(async (value) => {
await saveToAPI(value)
}, {
wait: 500,
onSuccess: (result, debouncer) => {
// 每次成功執行後呼叫
console.log('非同步函數已執行', debouncer.getSuccessCount())
},
onSettled: (debouncer) => {
// 每次執行嘗試後呼叫
console.log('非同步函數已結算', debouncer.getSettledCount())
},
onError: (error) => {
// 如果非同步函數拋出錯誤則呼叫
console.error('非同步函數失敗:', error)
}
})
const asyncDebouncer = new AsyncDebouncer(async (value) => {
await saveToAPI(value)
}, {
wait: 500,
onSuccess: (result, debouncer) => {
// 每次成功執行後呼叫
console.log('非同步函數已執行', debouncer.getSuccessCount())
},
onSettled: (debouncer) => {
// 每次執行嘗試後呼叫
console.log('非同步函數已結算', debouncer.getSettledCount())
},
onError: (error) => {
// 如果非同步函數拋出錯誤則呼叫
console.error('非同步函數失敗:', error)
}
})
onSuccess 回呼在防抖函數每次成功執行後呼叫,而 onError 回呼在非同步函數拋出錯誤時呼叫。onSettled 回呼在每次執行嘗試後呼叫,無論成功或失敗。這些回呼對於追蹤執行計數、更新 UI 狀態、處理錯誤、執行清理操作和記錄執行指標特別有用。
非同步防抖器提供了一種強大的方式來處理非同步操作與防抖,相比同步版本提供了幾個關鍵優勢。雖然同步防抖器非常適合 UI 事件和即時反饋,但非同步版本專門設計用於處理 API 呼叫、資料庫操作和其他非同步任務。
返回值處理 與返回 void 的同步防抖器不同,非同步版本允許你捕獲並使用防抖函數的返回值。這在需要處理 API 呼叫或其他非同步操作的結果時特別有用。maybeExecute 方法返回一個 Promise,該 Promise 解析為函數的返回值,允許你等待結果並適當處理。
增強的回呼系統 非同步防抖器提供比同步版本的單一 onExecute 回呼更複雜的回呼系統。此系統包括:
例如,如果你正在更新使用者個人資料,然後立即獲取其更新後的資料,非同步防抖器將確保獲取操作等待更新完成,防止可能獲取過時資料的競態條件。
這是一個基本範例,展示如何將非同步防抖器用於搜尋操作:
const debouncedSearch = asyncDebounce(
async (searchTerm: string) => {
const results = await fetchSearchResults(searchTerm)
return results
},
{
wait: 500,
onSuccess: (results, debouncer) => {
console.log('搜尋成功:', results)
},
onError: (error, debouncer) => {
console.error('搜尋失敗:', error)
}
}
)
// 用法
const results = await debouncedSearch('query')
const debouncedSearch = asyncDebounce(
async (searchTerm: string) => {
const results = await fetchSearchResults(searchTerm)
return results
},
{
wait: 500,
onSuccess: (results, debouncer) => {
console.log('搜尋成功:', results)
},
onError: (error, debouncer) => {
console.error('搜尋失敗:', error)
}
}
)
// 用法
const results = await debouncedSearch('query')
非同步防抖器可以與各種模式結合以解決複雜問題:
狀態管理整合 當將非同步防抖器與狀態管理系統 (如 React 的 useState 或 Solid 的 createSignal) 一起使用時,你可以創建強大的模式來處理載入狀態、錯誤狀態和資料更新。防抖器的回呼提供了根據操作成功或失敗更新 UI 狀態的完美鉤點。
競態條件預防 單次飛行突變模式自然防止了許多情境下的競態條件。當應用程式的多個部分同時嘗試更新同一資源時,防抖器確保只有最近的更新實際發生,同時仍向所有呼叫者提供結果。
錯誤恢復 非同步防抖器的錯誤處理能力使其非常適合實現重試邏輯和錯誤恢復模式。你可以使用 onError 回呼來實現自定義錯誤處理策略,例如指數退避或後備機制。
每個框架適配器都提供建立在核心防抖功能之上的鉤子,以與框架的狀態管理系統整合。每個框架都有可用的鉤子,如 createDebouncer、useDebouncedCallback、useDebouncedState 或 useDebouncedValue。
以下是一些範例:
import { useDebouncer, useDebouncedCallback, useDebouncedValue } from '@tanstack/react-pacer'
// 用於完全控制的低階鉤子
const debouncer = useDebouncer(
(value: string) => saveToDatabase(value),
{ wait: 500 }
)
// 用於基本用例的簡單回呼鉤子
const handleSearch = useDebouncedCallback(
(query: string) => fetchSearchResults(query),
{ wait: 500 }
)
// 用於響應式狀態管理的基於狀態的鉤子
const [instantState, setInstantState] = useState('')
const [debouncedValue] = useDebouncedValue(
instantState, // 要防抖的值
{ wait: 500 }
)
import { useDebouncer, useDebouncedCallback, useDebouncedValue } from '@tanstack/react-pacer'
// 用於完全控制的低階鉤子
const debouncer = useDebouncer(
(value: string) => saveToDatabase(value),
{ wait: 500 }
)
// 用於基本用例的簡單回呼鉤子
const handleSearch = useDebouncedCallback(
(query: string) => fetchSearchResults(query),
{ wait: 500 }
)
// 用於響應式狀態管理的基於狀態的鉤子
const [instantState, setInstantState] = useState('')
const [debouncedValue] = useDebouncedValue(
instantState, // 要防抖的值
{ wait: 500 }
)
import { createDebouncer, createDebouncedSignal } from '@tanstack/solid-pacer'
// 用於完全控制的低階鉤子
const debouncer = createDebouncer(
(value: string) => saveToDatabase(value),
{ wait: 500 }
)
// 用於狀態管理的基於信號的鉤子
const [searchTerm, setSearchTerm, debouncer] = createDebouncedSignal('', {
wait: 500,
onExecute: (debouncer) => {
console.log('總執行次數:', debouncer.getExecutionCount())
}
})
import { createDebouncer, createDebouncedSignal } from '@tanstack/solid-pacer'
// 用於完全控制的低階鉤子
const debouncer = createDebouncer(
(value: string) => saveToDatabase(value),
{ wait: 500 }
)
// 用於狀態管理的基於信號的鉤子
const [searchTerm, setSearchTerm, debouncer] = createDebouncedSignal('', {
wait: 500,
onExecute: (debouncer) => {
console.log('總執行次數:', debouncer.getExecutionCount())
}
})
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.