Queuing stores operations in an ordered buffer and processes them individually. It is the primary Pacer strategy for work that should not be discarded when calls arrive faster than they can run.
Queues are lossless only while they have capacity. A finite maxSize, explicit clearing, expiration, or a processing error can still remove or reject work.
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]The queue can process automatically with a delay between items, or remain stopped for manual processing.
Choose queuing when:
Choose another utility when:
Use the queued state or value API when queue contents drive the UI. Use the instance API for ordering, capacity, expiration, pause, resume, flush, and manual processing.
Invoke the helper in a template. Named arguments supply options, and the second positional argument selects state. Removing the helper invocation runs cleanup. The example imports application operations from ./api.
import Component from '@glimmer/component'
import { on } from '@ember/modifier'
import { fn } from '@ember/helper'
import { useQueuer } from '@tanstack/ember-pacer'
import type { QueuerState } from '@tanstack/ember-pacer'
import { processJob } from './api'
const select = (state: QueuerState<string>) => ({
items: state.items,
isRunning: state.isRunning,
})
export default class Example extends Component {
<template>
{{#let (useQueuer processJob select wait=500) as |queue|}}
<button {{on 'click' (fn queue.addItem 'task')}}>Add job</button>
<output>{{queue.state.items.length}}</output>
{{/let}}
</template>
}The focused TypeScript snippets below demonstrate the core Queuer class re-exported by the adapter. In a component, use useQueuer as above to own the instance, pass configuration as named arguments, and pass the yielded instance to event handlers. Core class instances require explicit cleanup.
Pass initialItems when work is already available at creation time. The queue applies its normal insertion and capacity rules, and automatic processing begins after the helper's first render unless started: false is set. Initial callbacks can safely update tracked component state.
useQueuedState yields the queue with items selected by default. Read queue.state.items and call queue.addItem().
Automatic processing uses addItemsTo to choose where new items enter and getItemsFrom to choose where items leave.
FIFO processes the oldest item first. This is the default.
import { Queuer } from '@tanstack/ember-pacer'
const queuer = new Queuer(processItem, {
addItemsTo: 'back',
getItemsFrom: 'front',
started: false,
})
queuer.addItem(1)
queuer.addItem(2)
queuer.addItem(3)
queuer.start() // Processes 1, 2, 3.LIFO processes the newest item first.
import { Queuer } from '@tanstack/ember-pacer'
const queuer = new Queuer(processItem, {
addItemsTo: 'back',
getItemsFrom: 'back',
started: false,
})
queuer.addItem(1)
queuer.addItem(2)
queuer.addItem(3)
queuer.start() // Processes 3, 2, 1.Provide getPriority to process higher numeric priorities first. Priority ordering takes precedence over front and back retrieval.
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.Queues start automatically by default. The first accepted item processes immediately, then wait controls the delay before later items.
import { Queuer } from '@tanstack/ember-pacer'
const queuer = new Queuer(processItem, {
wait: 1000,
})Set started: false to collect items before processing:
import { Queuer } from '@tanstack/ember-pacer'
const queuer = new Queuer(processItem, { started: false })
queuer.addItem(1)
queuer.addItem(2)
queuer.start()
queuer.stop()stop() cancels the scheduled tick and retains queued items. start() resumes automatic processing.
For manual control:
Set maxSize to bound the number of waiting items. An item added to a full queue is rejected, addItem() returns false, and onReject runs.
import { Queuer } from '@tanstack/ember-pacer'
const queuer = new Queuer(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) // falseThe active synchronous execution is not part of size; size counts items still waiting in the queue.
Use expirationDuration to remove items that have waited too long:
import { Queuer } from '@tanstack/ember-pacer'
const queuer = new Queuer(processItem, {
expirationDuration: 5000,
onExpire: (item) => {
console.log('Expired:', item)
},
})Use getIsExpired for custom logic:
import { Queuer } from '@tanstack/ember-pacer'
const queuer = new Queuer(processItem, {
getIsExpired: (item, addedAt) => Date.now() - addedAt > item.maxAge,
})Expiration is checked while the automatic processing loop runs. A stopped queue evaluates stale items when processing resumes.
flush() processes waiting items immediately without the configured delay. Pass a count to process only part of the queue.
queuer.flush() // Process all waiting items.
queuer.flush(2) // Process at most two waiting items.flushAsBatch() removes all waiting items and passes them to a separate batch function:
queuer.flushAsBatch((items) => {
saveItems(items)
})clear() removes all waiting items without processing them. It does not change whether the queue is running.
queuer.clear()reset() restores state to the default running, empty queue. It does not clear an already scheduled timeout. Call stop() before reset() when scheduled work must be canceled.
queuer.stop()
queuer.reset()Use setOptions() to update future behavior. Changing started through setOptions() does not call start() or stop().
queuer.setOptions({ wait: 250, maxSize: 20 })
queuer.start()The wait option may be a function that receives the queuer instance:
import { Queuer } from '@tanstack/ember-pacer'
const queuer = new Queuer(processItem, {
wait: (queuer) => (queuer.store.state.size > 20 ? 50 : 250),
})Use callbacks for queue events:
The adapter stops automatic processing when its owner is destroyed. Providing onUnmount replaces that default cleanup, so a custom callback must perform every required lifecycle action. When custom cleanup flushes work, remember that user callbacks can run while the component is being destroyed.
The adapter subscribes only to the state returned by the selector argument. Without a selector, the adapter state is empty. Use the helper's second positional argument to select fields, as shown above. Read those fields from the yielded instance's .state in the template.
import Component from '@glimmer/component'
import { on } from '@ember/modifier'
import { fn } from '@ember/helper'
import { useQueuer } from '@tanstack/ember-pacer'
import type { QueuerState } from '@tanstack/ember-pacer'
import { processJob } from './api'
const select = (state: QueuerState<string>) => ({
items: state.items,
isRunning: state.isRunning,
})
export default class Example extends Component {
<template>
{{#let (useQueuer processJob select wait=500) as |queue|}}
<button {{on 'click' (fn queue.addItem 'task')}}>Add job</button>
<output>{{queue.state.items.length}}</output>
{{/let}}
</template>
}The contextual utility.Subscribe helper selects state for a child template without subscribing the utility owner.
Option functions and lifecycle callbacks receive the underlying public utility instance. The .store.state reads inside those callbacks in the examples above are supported. Rendering code should read the selected adapter state shown here.
initialState can restore selected queue state that your app has persisted. If it includes items, they take precedence over initialItems; initialState.isRunning likewise takes precedence over started. Restore only durable fields. Pending timers are not restored.
Commonly useful state includes:
See the Ember API reference for adapter signatures and the public core reference for complete option and state types.