본문으로 건너뛰기

Preact 큐잉 가이드

큐잉은 작업을 순서가 있는 버퍼에 저장하고 개별적으로 처리합니다. 실행 속도보다 호출이 더 빠르게 들어와도 작업을 버리면 안 되는 경우에 사용하는 Pacer의 주요 전략입니다.

큐에 용량이 남아 있을 때만 작업이 유실되지 않습니다. 유한한 maxSize, 명시적인 비우기, 만료, 처리 오류로 인해 작업이 제거되거나 거부될 수 있습니다.

큐잉의 작동 방식

Queuing (process one item every 2 ticks)
Timeline: [1 second per tick]
Calls: ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
Queue: [ABC] [BC] [BCDE] [DE] [E] []
Executed: ✅ ✅ ✅ ✅ ✅ ✅
[======================================================]
^ Accepted items remain queued until processed

[Items arrive] [Process steadily] [Empty]

큐는 항목 사이에 지연 시간을 두고 자동으로 처리하거나 수동 처리를 위해 중지된 상태로 유지할 수 있습니다.

큐잉을 사용해야 하는 경우

다음과 같은 경우 큐잉을 선택합니다.

  • 수락된 모든 작업을 개별적으로 실행해야 하는 경우
  • 처리 순서가 중요한 경우
  • 들어오는 작업이 일시적으로 처리 용량을 초과할 수 있는 경우
  • 최대 버퍼 크기를 기준으로 초과 작업을 명시적으로 거부해야 하는 경우
  • FIFO, LIFO 또는 우선순위 정렬이 필요한 경우

다음과 같은 경우 다른 유틸리티를 선택합니다.

  • 마지막 호출만 중요한 경우 디바운싱을 사용합니다.
  • 작업을 일정하게 실행하는 동안 중간 호출을 버려도 되는 경우 스로틀링을 사용합니다.
  • 시간 창 할당량을 초과한 호출을 거부해야 하는 경우 요청률 제한을 사용합니다.
  • 여러 항목을 함께 실행해야 하는 경우 배칭을 사용합니다.
  • 작업이 Promise를 반환하거나 동시에 실행되어야 하는 경우 비동기 큐잉을 사용합니다.

API 선택하기

  • Preact 상태에 연결된 큐가 필요하면 useQueuedState 또는 useQueuedValue
  • 큐 수명 주기와 순서를 직접 제어하려면 useQueuer

큐 내용이 UI를 구동한다면 큐 상태 또는 값 API를 사용합니다. 순서, 용량, 만료, 일시 중지, 재개, 플러시, 수동 처리가 필요하면 인스턴스 API를 사용합니다.

Preact 예제

import { useQueuedState } from '@tanstack/preact-pacer'

function JobQueue() {
const [items, addItem, queue] = useQueuedState(
processJob,
{ wait: 500 },
(state) => ({
items: state.items,
isRunning: state.isRunning,
}),
)

return (
<>
<button onClick={() => addItem(nextJob())}>Add job</button>
<button
onClick={() => (queue.state.isRunning ? queue.stop() : queue.start())}
>
{queue.state.isRunning ? 'Pause' : 'Resume'} ({items.length})
</button>
</>
)
}

이 가이드에서 이후에 다루는 핵심 코드 조각은 useQueuer를 사용하며 컴포넌트나 다른 훅 내부에서 실행한다고 가정합니다.

생성 시점에 이미 작업이 있다면 initialItems를 전달합니다. 큐는 일반적인 삽입 및 용량 규칙을 적용하며 started: false를 설정하지 않으면 자동 처리를 즉시 시작할 수 있습니다.

항목 순서 지정하기

자동 처리는 addItemsTo로 새 항목이 들어올 위치를 선택하고 getItemsFrom으로 항목이 나갈 위치를 선택합니다.

FIFO

FIFO는 가장 오래된 항목부터 처리합니다. 기본 동작입니다.

const queuer = useQueuer(processItem, {
addItemsTo: 'back',
getItemsFrom: 'front',
started: false,
})

queuer.addItem(1)
queuer.addItem(2)
queuer.addItem(3)
queuer.start() // Processes 1, 2, 3.

LIFO

LIFO는 가장 최근 항목부터 처리합니다.

const queuer = useQueuer(processItem, {
addItemsTo: 'back',
getItemsFrom: 'back',
started: false,
})

queuer.addItem(1)
queuer.addItem(2)
queuer.addItem(3)
queuer.start() // Processes 3, 2, 1.

우선순위

숫자 우선순위가 높은 항목부터 처리하려면 getPriority를 제공합니다. 우선순위 정렬은 앞뒤 검색 설정보다 우선합니다.

type Task = { name: string; priority: number }

const queuer = useQueuer<Task>(processTask, {
getPriority: (task) => task.priority,
started: false,
})

queuer.addItem({ name: 'low', priority: 1 })
queuer.addItem({ name: 'high', priority: 3 })
queuer.addItem({ name: 'medium', priority: 2 })
queuer.start() // Processes high, medium, low.

자동 및 수동 처리

기본적으로 큐는 자동으로 시작합니다. 수락된 첫 항목은 즉시 처리되며 이후 항목부터 wait가 지연 시간을 제어합니다.

const queuer = useQueuer(processItem, {
wait: 1000,
})

처리 전에 항목을 수집하려면 started: false를 설정합니다.

const queuer = useQueuer(processItem, { started: false })

queuer.addItem(1)
queuer.addItem(2)
queuer.start()
queuer.stop()

stop()은 예약된 틱을 취소하고 큐 항목을 유지합니다. start()는 자동 처리를 재개합니다.

수동 제어에는 다음 메서드를 사용합니다.

  • execute()는 다음 항목을 제거하고 즉시 처리합니다.
  • getNextItem()은 다음 항목을 처리하지 않고 제거해 반환합니다.
  • peekNextItem()은 다음 항목을 제거하지 않고 반환합니다.
  • peekAllItems()는 현재 큐의 복사본을 반환합니다.

용량과 거부

대기 항목 수를 제한하려면 maxSize를 설정합니다. 가득 찬 큐에 추가한 항목은 거부되고 addItem()false를 반환하며 onReject가 실행됩니다.

const queuer = useQueuer(processItem, {
maxSize: 2,
started: false,
onReject: (item, queuer) => {
console.log('Rejected:', item)
console.log('Total rejections:', queuer.store.state.rejectionCount)
},
})

queuer.addItem(1) // true
queuer.addItem(2) // true
queuer.addItem(3) // false

활성 동기 실행은 size에 포함되지 않습니다. size는 큐에서 아직 대기 중인 항목을 셉니다.

오래된 항목 만료시키기

너무 오래 대기한 항목을 제거하려면 expirationDuration을 사용합니다.

const queuer = useQueuer(processItem, {
expirationDuration: 5000,
onExpire: (item) => {
console.log('Expired:', item)
},
})

사용자 정의 로직이 필요하면 getIsExpired를 사용합니다.

const queuer = useQueuer(processItem, {
getIsExpired: (item, addedAt) => Date.now() - addedAt > item.maxAge,
})

자동 처리 루프가 실행되는 동안 만료 여부를 확인합니다. 중지된 큐는 처리가 재개될 때 오래된 항목을 평가합니다.

플러시, 비우기, 재설정하기

플러시

flush()는 설정된 지연 시간 없이 대기 항목을 즉시 처리합니다. 큐의 일부만 처리하려면 개수를 전달합니다.

queuer.flush() // Process all waiting items.
queuer.flush(2) // Process at most two waiting items.

flushAsBatch()는 모든 대기 항목을 제거하고 별도의 배치 함수에 전달합니다.

queuer.flushAsBatch((items) => {
saveItems(items)
})

비우기

clear()는 모든 대기 항목을 처리하지 않고 제거합니다. 큐의 실행 여부는 변경하지 않습니다.

queuer.clear()

재설정

reset()은 상태를 기본값인 실행 중인 빈 큐로 복원하지만 이미 예약된 타임아웃은 지우지 않습니다. 예약된 작업을 취소해야 한다면 먼저 stop()을 호출한 다음 reset()으로 상태를 복원합니다.

queuer.stop()
queuer.reset()

큐 설정 및 관찰하기

향후 동작을 업데이트하려면 setOptions()를 사용합니다. startedsetOptions()로 변경해도 start()stop()을 호출하지 않습니다.

queuer.setOptions({ wait: 250, maxSize: 20 })
queuer.start()

wait 옵션에는 큐어 인스턴스를 받는 함수를 사용할 수 있습니다.

const queuer = useQueuer(processItem, {
wait: (queuer) => (queuer.store.state.size > 20 ? 50 : 250),
})

큐 이벤트에는 다음 콜백을 사용합니다.

  • onItemsChange: 항목이 추가되거나 제거되었습니다.
  • onExecute: 항목이 처리되었습니다.
  • onReject: 항목이 거부되었습니다.
  • onExpire: 항목이 만료되었습니다.

Preact 수명 주기

어댑터는 소유자가 제거될 때 자동 처리를 중지합니다. onUnmount를 제공하면 이 기본 정리 동작을 대체하므로 사용자 정의 콜백에서 필요한 모든 수명 주기 작업을 수행해야 합니다. 사용자 정의 정리 과정에서 작업을 플러시하면 컴포넌트가 제거되는 동안 사용자 콜백이 실행될 수 있다는 점에 유의해야 합니다.

반응형 상태

어댑터는 셀렉터 인수가 반환한 상태만 구독합니다. 셀렉터가 없으면 어댑터 상태는 비어 있습니다. 컴포넌트나 다른 훅의 최상위 수준에서 유틸리티를 만들고 뷰에서 사용하는 필드만 선택합니다.

const queuer = useQueuer(processItem, { wait: 250 }, (state) => ({
size: state.size,
isRunning: state.isRunning,
}))

console.log(queuer.state.size, queuer.state.isRunning)

옵션 함수와 수명 주기 콜백은 기반 공개 유틸리티 인스턴스를 받습니다. 위 예제처럼 해당 콜백 안에서 .store.state를 읽는 방식을 지원합니다. 렌더링 코드는 여기에 나온 선택된 어댑터 상태를 읽어야 합니다.

initialState로 앱에서 유지한 선택된 큐 상태를 복원할 수 있습니다. items가 포함되어 있으면 initialItems보다 우선하며, 마찬가지로 initialState.isRunningstarted보다 우선합니다. 지속 가능한 필드만 복원합니다. 대기 중인 타이머는 복원되지 않습니다.

일반적으로 유용한 상태에는 다음 항목이 포함됩니다.

  • items, size: 아직 대기 중인 항목입니다.
  • isRunning: 자동 처리가 활성화되었는지 여부입니다.
  • isIdle: 실행 중인 큐가 비어 있는지 여부입니다.
  • isFull: maxSize에 도달했는지 여부입니다.
  • executionCount: 래핑된 함수가 성공적으로 반환한 항목 수입니다.
  • rejectionCount, expirationCount: 처리하지 않고 제거된 항목 수입니다.
  • status: 'idle', 'running', 'stopped' 중 하나입니다.

어댑터 시그니처는 Preact API 레퍼런스를, 전체 옵션 및 상태 타입은 공개 코어 레퍼런스를 참고합니다.