Preact 비동기 배칭 가이드
비동기 배칭은 배칭 가이드에서 설명한 수집 및 트리거 동작을 유지하면서 Promise 결과, 재시도, 오류 콜백, 실패 항목 추적, 진행 중인 작업에 대한 제어 기능을 추가합니다.
하나의 비동기 작업으로 수집한 여러 항목을 함께 처리해야 할 때 사용합니다. 각 항목을 개별적으로 실행해야 하거나 동시 실행 수를 제한해야 한다면 비동기 큐를 사용합니다.
비동기 배칭의 작동 방식
설정된 트리거 중 하나가 작동할 때까지 항목을 수집합니다.
add A ─── add B ─── add C
│ │ │
└─ wait reset └─ maxSize reached
│
└─ execute [A, B, C]
다음과 같은 경우 배치를 실행합니다.
- 길이가
maxSize에 도달하는 경우 getShouldExecute(items, batcher)가true를 반환하는 경우wait밀리초 동안 새 항목이 추가되지 않는 경우
maxSize와 wait의 기본값은 모두 Infinity이므로 트리거를 하나 이상 설정하거나 flush()를 직접 호출해야 합니다. 대기 타이머는 항목을 추가할 때마다 다시 시작합니다. 이 타이머는 유휴 시간을 측정하며, 가장 오래된 항목의 최대 보관 시간을 측정하지 않습니다.
API 선택하기
- 항목을 추가하려면
useAsyncBatchedCallback - 플러시, 실패 항목, 선택한 실행 상태가 필요하면
useAsyncBatcher
Preact 예제
import { useAsyncBatcher } from '@tanstack/preact-pacer'
function AnalyticsButton() {
const batcher = useAsyncBatcher(
sendEvents,
{ maxSize: 20, wait: 1000 },
(state) => ({
size: state.size,
isExecuting: state.isExecuting,
}),
)
return (
<button onClick={() => void batcher.addItem({ type: 'click' })}>
Track ({batcher.state.size} pending)
</button>
)
}
이 가이드에서 이후에 다루는 핵심 코드 조각은 useAsyncBatcher를 사용하며 컴포넌트나 다른 훅 내부에서 실행한다고 가정합니다.
Promise 결과
flush()는 배치 함수의 결과를 반환하며 특정 배치를 기다리는 가장 명확한 방법입니다.
addItem()도 Promise를 반환하지만 개별 항목의 결과를 받는 수단으로 간주해서는 안 됩니다.
- 해당 항목 추가로
maxSize에 도달하거나getShouldExecute를 충족하면, 반환된 Promise가 트리거된 실행을 담당하며 배치 결과로 이행됩니다. - 대기 타이머만 예약하면 반환된 Promise는 이후의 배치 결과 없이 이행됩니다.
- 다른 항목을 추가하면 타이머가 재설정되고 함께 실행되는 항목이 달라질 수 있습니다.
모든 항목 추가에서 최종 배치 결과를 확인해야 한다면 onSuccess, onError, onSettled를 사용합니다.
배치 경계와 중첩 작업
배처는 비동기 함수를 호출하기 전에 현재 항목을 복사하고 비웁니다. 해당 함수가 실행 중일 때 추가한 항목은 새 배치에 수집됩니다.
execute [A, B] ───────────────── finish
add C ─── add D ─── execute [C, D] ─── finish
첫 번째 배치가 완료되기 전에 두 번째 배치의 트리거가 작동하면 두 배치 함수가 겹쳐 실행될 수 있습니다. useAsyncBatcher에는 동시 실행 옵션이 없습니다. 중첩 실행이 안전하지 않다면 배처 외부에서 배치 실행을 직렬화하거나 완성된 배치를 비동기 큐로 전달합니다.
오류와 실패 항목
비동기 배처는 다음 콜백을 제공합니다.
- 성공한 후에는
onSuccess(result, batch, batcher)를 호출합니다. - 배치 재시도가 실패한 후에는
onError(error, batch, batcher)를 호출합니다. - 어느 결과든 완료된 후에는
onSettled(batch, batcher)를 호출합니다. - 항목을 추가하거나 실행을 위해 제거할 때는
onItemsChange(batcher)를 호출합니다.
onError가 없으면 throwOnError의 기본값이 true이므로 실패 시 flush() 또는 크기 기준 실행을 트리거한 addItem()이 거부됩니다. onError를 제공하면 이 기본값이 false로 바뀌고 Promise는 undefined로 이행됩니다.
항목은 실행 전에 대기 중인 컬렉션에서 제거됩니다. 실패한 배치는 자동으로 큐에 다시 추가되지 않습니다. 해당 항목은 failedItems에 추가되고 peekFailedItems()로 확인할 수 있으며, clear() 또는 이후 실행이 이 컬렉션을 비울 때까지 유지됩니다.
const failed = batcher.peekFailedItems()
for (const item of failed) {
saveForManualRecovery(item)
}
멱등성이 없는 작업은 실패한 배치를 다시 제출하기 전에 서버의 처리 결과를 확인합니다.
배치 재시도하기
asyncRetryerOptions로 각 배치 실행의 재시도를 설정합니다.
const batcher = useAsyncBatcher(sendEvents, {
maxSize: 20,
wait: 1000,
asyncRetryerOptions: {
maxAttempts: 3,
backoff: 'exponential',
baseWait: 500,
jitter: 0.2,
},
})
maxAttempts에는 첫 번째 시도가 포함되며 모든 재시도에는 복사된 동일한 배치가 전달됩니다. 부수 효과가 있는 작업을 재시도하기 전에 비동기 재시도 가이드를 참고합니다.
플러시, 취소, 비우기
flush()는 대기 중인 타이머를 지우고 현재 항목을 즉시 실행합니다.cancel()은 대기 중인 타이머를 지우지만 수집한 항목은 유지합니다.clear()는 수집한 항목과 실패한 항목을 제거하지만 예약된 타이머는 지우지 않습니다.peekAllItems()는 현재 수집한 항목의 복사본을 반환합니다.
clear()는 타이머를 그대로 두므로 빈 타이머가 남지 않아야 한다면 cancel()에 이어 clear()를 사용합니다.
batcher.cancel()
batcher.clear()
나중에 타이머 또는 flush()가 항목을 찾지 못하면 배치 함수를 호출하지 않습니다.
활성 작업 중단하기
abort()는 활성 재시도기를 중단합니다. 대기 중인 배치를 취소하거나 수집한 항목을 제거하지는 않습니다. 취소가 전파되도록 배처의 시그널을 기반 API에 전달합니다.
const batcher = useAsyncBatcher(
async (events: Array<AnalyticsEvent>) => {
return fetch('/api/analytics/batch', {
method: 'POST',
body: JSON.stringify(events),
signal: batcher.getAbortSignal() ?? undefined,
})
},
{ maxSize: 20, wait: 1000 },
)
batcher.abort()
실행이 겹칠 때 특정 실행의 시그널이 필요하면 executeCount를 getAbortSignal()에 전달합니다.
안전하게 재설정하기
reset()은 기본 상태를 복원하지만 예약된 타이머를 지우거나 실행 중인 기반 작업의 중단을 보장하지 않습니다. 완전히 정리해야 한다면 먼저 수명 주기 메서드를 사용합니다.
batcher.cancel()
batcher.abort()
batcher.reset()
Preact 수명 주기
어댑터는 소유자가 제거될 때 대기 중인 타이머를 취소하고 활성 작업을 중단합니다. onUnmount를 제공하면 이 기본 정리 동작을 대체하므로 사용자 정의 콜백에서 필요한 모든 수명 주기 작업을 수행해야 합니다. 사용자 정의 정리 과정에서 작업을 플러시하면 컴포넌트가 제거되는 동안 사용자 콜백이 실행될 수 있다는 점에 유의해야 합니다.
설정과 반응형 상태
어댑터는 셀렉터 인수가 반환한 상태만 구독합니다. 셀렉터가 없으면 어댑터 상태는 비어 있습니다. 컴포넌트나 다른 훅의 최상위 수준에서 유틸리티를 만들고 뷰에서 사용하는 필드만 선택합니다.
const batcher = useAsyncBatcher(
sendEvents,
{ maxSize: 20, wait: 1000 },
(state) => ({
size: state.size,
isExecuting: state.isExecuting,
failedItems: state.failedItems,
}),
)
console.log(
batcher.state.size,
batcher.state.isExecuting,
batcher.state.failedItems,
)
옵션 함수와 수명 주기 콜백은 기반 공개 유틸리티 인스턴스를 받습니다. 위 예제처럼 해당 콜백 안에서 .store.state를 읽는 방식을 지원합니다. 렌더링 코드는 여기에 나온 선택된 어댑터 상태를 읽어야 합니다.
wait에는 숫자 또는 배처 인스턴스를 받는 함수를 사용할 수 있습니다. setOptions()는 새 옵션을 병합하고 asyncBatcherOptions()는 재사용 가능한 타입 검사 옵션 객체를 만듭니다.
배처를 일시 중지하는 데 started를 사용하지 마세요. 현재 아무 동작도 하지 않으므로 모든 addItem() 호출에서 설정된 트리거를 평가합니다.
앱에서 유지한 선택 상태를 복원하려면 initialState로 부분 스냅샷을 전달합니다. 이 스냅샷은 기본값과 병합됩니다. 지속 가능한 필드만 복원합니다. 대기 중인 타이머와 활성 실행은 복원되지 않습니다.
일반적인 상태에는 다음 항목이 포함됩니다.
items,size,isPending: 다음 배치와 그 타이머 상태입니다.isExecuting: 배치를 실행 중인 것으로 보고하는지 여부입니다.lastResult: 가장 최근의 성공 결과입니다.failedItems,totalItemsFailed: 실패 추적 정보입니다.successCount,errorCount,settleCount: 배치 결과 횟수입니다.totalItemsProcessed: 성공한 배치 실행의 항목 수입니다.
어댑터 시그니처는 Preact API 레퍼런스를, 전체 옵션 및 상태 타입은 공개 코어 레퍼런스를 참고합니다.