함수: useAsyncRateLimiter()
function useAsyncRateLimiter<TFn, TSelected>(
fn,
options,
selector): PreactAsyncRateLimiter<TFn, TSelected>;
정의 위치: preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:231
시간 윈도우 안에서 비동기 함수의 실행 횟수를 제한하는 AsyncRateLimiter 인스턴스를 생성하는 하위 수준 Preact 훅입니다.
유연하고 상태 관리 방식에 구애받지 않도록 설계되었습니다. 요청률 제한기 인스턴스만 반환하므로 어떤 상태 관리 솔루션(useState, Redux, Zustand, Jotai 등)과도 통합할 수 있습니다.
요청률 제한은 비동기 함수가 시간 윈도우 안에서 지정된 한도까지 실행되도록 허용한 뒤 윈도우가 지날 때까지 이후 호출을 차단합니다. API 요청률 제한 준수, 리소스 제약 관리 또는 비동기 작업 급증 제어에 유용합니다.
비동기가 아닌 RateLimiter와 달리 이 비동기 버전은 요청률이 제한된 함수의 값을 반환할 수 있습니다.
따라서 요청률 제한 함수 안에서 결과를 상태 변수에 설정하는 대신 maybeExecute 호출 결과를 사용하려는
API 호출 및 기타 비동기 작업에 적합합니다.
요청률 제한기는 두 가지 윈도우 유형을 지원합니다.
- 'fixed': 윈도우 기간이 지나면 초기화되는 엄격한 윈도우입니다. 윈도우 안의 모든 실행이 한도에 포함되며 기간이 지나면 윈도우가 완전히 초기화됩니다.
- 'sliding': 이전 실행이 만료됨에 따라 실행을 허용하는 롤링 윈도우입니다. 시간에 걸쳐 더 일정한 실행률을 제공합니다.
오류 처리:
onError핸들러를 제공하면 오류 및 요청률 제한기 인스턴스와 함께 호출됩니다.throwOnError가 true이면(onError 핸들러가 없을 때의 기본값) 오류가 발생합니다.throwOnError가 false이면(onError 핸들러가 있을 때의 기본값) 오류가 처리된 것으로 간주됩니다.- onError와 throwOnError를 함께 사용할 수 있으며, 오류가 발생하기 전에 핸들러가 호출됩니다.
- 내부 AsyncRateLimiter 인스턴스를 사용해 오류 상태를 확인할 수 있습니다.
- 요청률 제한 거부(한도 초과 시)는
onReject핸들러를 통해 실행 오류와 별도로 처리됩니다.
상태 관리와 셀렉터
훅은 반응형 상태 관리에 TanStack Store를 사용합니다. 다음 두 가지 방식으로 상태 변경을 구독할 수 있습니다.
1. rateLimiter.Subscribe HOC 사용(컴포넌트 트리 구독에 권장)
Subscribe HOC를 사용하면 컴포넌트 트리 깊은 곳의 상태 변경을 구독할 때
훅에 셀렉터를 전달할 필요가 없습니다. 자식 컴포넌트에서 상태를
구독하려는 경우에 적합합니다.
2. selector 매개변수 사용(훅 수준 구독)
selector 매개변수를 사용하면 어떤 상태 변경이 재렌더링을 트리거할지 지정할 수 있으며
관련 없는 상태가 변경될 때 불필요한 재렌더링을 방지하여 훅 수준의 성능을 최적화합니다.
기본적으로 반응형 상태 구독은 없습니다. 상태 추적을 명시적으로 활성화하려면
셀렉터 함수를 제공하거나 Subscribe HOC를 사용해야 합니다. 이렇게 하면 불필요한
재렌더링을 방지하고 컴포넌트가 업데이트되는 시점을 완전히 제어할 수 있습니다.
사용 가능한 상태 속성:
errorCount: 오류가 발생한 함수 실행 횟수executionTimes: 요청률 제한 계산을 위해 실행이 발생한 시점의 타임스탬프 배열isExecuting: 요청률이 제한된 함수가 현재 비동기로 실행 중인지 여부lastResult: 가장 최근에 성공한 함수 실행 결과rejectionCount: 요청률 제한으로 거부된 함수 실행 횟수settleCount: 성공 또는 오류로 완료된 함수 실행 횟수successCount: 성공적으로 완료된 함수 실행 횟수
언마운트 동작
기본적으로 컴포넌트가 언마운트되면 훅은 진행 중인 실행을 중단합니다.
getAbortSignal()의 중단 신호를 내부 작업(예: fetch)에 전달한 경우에만 Abort가 해당 작업을 취소합니다.
이를 사용자 지정하려면 onUnmount 옵션을 사용합니다.
타입 매개변수
TFn
TFn extends AnyAsyncFunction
TSelected
TSelected = {
}
매개변수
fn
TFn
options
PreactAsyncRateLimiterOptions<TFn, TSelected>
selector
(state) => TSelected
반환값
PreactAsyncRateLimiter<TFn, TSelected>
예시
// 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
);
// Subscribe to state changes deep in component tree using Subscribe HOC
<asyncRateLimiter.Subscribe selector={(state) => ({ rejectionCount: state.rejectionCount })}>
{({ rejectionCount }) => (
<div>Rejections: {rejectionCount}</div>
)}
</asyncRateLimiter.Subscribe>
// Opt-in to re-render when execution state changes at hook level (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;