queue
Queuer
ClassUnlike Rate Limiting, Throttling, and Debouncing which drop executions when they occur too frequently, queuers ensure that every operation is processed. They provide a way to manage and control the flow of operations without losing any requests. This makes them ideal for scenarios where data loss is unacceptable. This guide will cover the Queueing concepts of TanStack Pacer.
Queueing ensures that every operation is eventually processed, even if they come in faster than they can be handled. Unlike the other execution control techniques that drop excess operations, queueing buffers operations in an ordered list and processes them according to specific rules. This makes queueing the only "lossless" execution control technique in TanStack Pacer.
Queueing (processing one item every 2 ticks)
Timeline: [1 second per tick]
Calls: ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
Queue: [ABC] [BC] [BCDE] [DE] [E] []
Executed: ✅ ✅ ✅ ✅ ✅ ✅
[=================================================================]
^ Unlike rate limiting/throttling/debouncing,
ALL calls are eventually processed in order
[Items queue up] [Process steadily] [Empty]
when busy one by one queue
Queueing (processing one item every 2 ticks)
Timeline: [1 second per tick]
Calls: ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
Queue: [ABC] [BC] [BCDE] [DE] [E] []
Executed: ✅ ✅ ✅ ✅ ✅ ✅
[=================================================================]
^ Unlike rate limiting/throttling/debouncing,
ALL calls are eventually processed in order
[Items queue up] [Process steadily] [Empty]
when busy one by one queue
Queueing is particularly important when you need to ensure that every operation is processed, even if it means introducing some delay. This makes it ideal for scenarios where data consistency and completeness are more important than immediate execution.
Common use cases include:
Queueing might not be the best choice when:
Tip
If you're currently using rate limiting, throttling, or debouncing but finding that dropped operations are causing problems, queueing is likely the solution you need.
TanStack Pacer provides queueing through the simple queue function and the more powerful Queuer class. While other execution control techniques typically favor their function-based APIs, queueing often benefits from the additional control provided by the class-based API.
The queue function provides a simple way to create an always-running queue that processes items as they're added:
import { queue } from '@tanstack/pacer'
// Create a queue that processes items every second
const processItems = queue<number>({
wait: 1000,
onUpdate: (queuer) => {
console.log('Current queue:', queuer.getAllItems())
}
})
// Add items to be processed
processItems(1) // Processed immediately
processItems(2) // Processed after 1 second
processItems(3) // Processed after 2 seconds
import { queue } from '@tanstack/pacer'
// Create a queue that processes items every second
const processItems = queue<number>({
wait: 1000,
onUpdate: (queuer) => {
console.log('Current queue:', queuer.getAllItems())
}
})
// Add items to be processed
processItems(1) // Processed immediately
processItems(2) // Processed after 1 second
processItems(3) // Processed after 2 seconds
While the queue function is simple to use, it only provides a basic always-running queue through the addItem method. For most use cases, you'll want the additional control and features provided by the Queuer class.
The Queuer class provides complete control over queue behavior and processing:
import { Queuer } from '@tanstack/pacer'
// Create a queue that processes items every second
const queue = new Queuer<number>({
wait: 1000, // Wait 1 second between processing items
onUpdate: (queuer) => {
console.log('Current queue:', queuer.getAllItems())
}
})
// Start processing
queue.start()
// Add items to be processed
queue.addItem(1)
queue.addItem(2)
queue.addItem(3)
// Items will be processed one at a time with 1 second delay between each
// Output:
// Processing: 1 (immediately)
// Processing: 2 (after 1 second)
// Processing: 3 (after 2 seconds)
import { Queuer } from '@tanstack/pacer'
// Create a queue that processes items every second
const queue = new Queuer<number>({
wait: 1000, // Wait 1 second between processing items
onUpdate: (queuer) => {
console.log('Current queue:', queuer.getAllItems())
}
})
// Start processing
queue.start()
// Add items to be processed
queue.addItem(1)
queue.addItem(2)
queue.addItem(3)
// Items will be processed one at a time with 1 second delay between each
// Output:
// Processing: 1 (immediately)
// Processing: 2 (after 1 second)
// Processing: 3 (after 2 seconds)
What makes TanStack Pacer's Queuer unique is its ability to adapt to different use cases through its position-based API. The same Queuer can behave as a traditional queue, a stack, or a double-ended queue, all through the same consistent interface.
The default behavior where items are processed in the order they were added. This is the most common queue type and follows the principle that the first item added should be the first one processed.
FIFO Queue Visualization:
Entry → [A][B][C][D] → Exit
⬇️ ⬆️
New items Items are
added here processed here
Timeline: [1 second per tick]
Calls: ⬇️ ⬇️ ⬇️ ⬇️
Queue: [ABC] [BC] [C] []
Processed: A B C
FIFO Queue Visualization:
Entry → [A][B][C][D] → Exit
⬇️ ⬆️
New items Items are
added here processed here
Timeline: [1 second per tick]
Calls: ⬇️ ⬇️ ⬇️ ⬇️
Queue: [ABC] [BC] [C] []
Processed: A B C
FIFO queues are ideal for:
const queue = new Queuer<number>({
addItemsTo: 'back', // default
getItemsFrom: 'front', // default
})
queue.addItem(1) // [1]
queue.addItem(2) // [1, 2]
// Processes: 1, then 2
const queue = new Queuer<number>({
addItemsTo: 'back', // default
getItemsFrom: 'front', // default
})
queue.addItem(1) // [1]
queue.addItem(2) // [1, 2]
// Processes: 1, then 2
By specifying 'back' as the position for both adding and retrieving items, the queuer behaves like a stack. In a stack, the most recently added item is the first one to be processed.
LIFO Stack Visualization:
⬆️ Process
[D] ← Most recently added
[C]
[B]
[A] ← First added
⬇️ Entry
Timeline: [1 second per tick]
Calls: ⬇️ ⬇️ ⬇️ ⬇️
Queue: [ABC] [AB] [A] []
Processed: C B A
LIFO Stack Visualization:
⬆️ Process
[D] ← Most recently added
[C]
[B]
[A] ← First added
⬇️ Entry
Timeline: [1 second per tick]
Calls: ⬇️ ⬇️ ⬇️ ⬇️
Queue: [ABC] [AB] [A] []
Processed: C B A
Stack behavior is particularly useful for:
const stack = new Queuer<number>({
addItemsTo: 'back', // default
getItemsFrom: 'back', // override default for stack behavior
})
stack.addItem(1) // [1]
stack.addItem(2) // [1, 2]
// Items will process in order: 2, then 1
stack.getNextItem('back') // get next item from back of queue instead of front
const stack = new Queuer<number>({
addItemsTo: 'back', // default
getItemsFrom: 'back', // override default for stack behavior
})
stack.addItem(1) // [1]
stack.addItem(2) // [1, 2]
// Items will process in order: 2, then 1
stack.getNextItem('back') // get next item from back of queue instead of front
Priority queues add another dimension to queue ordering by allowing items to be sorted based on their priority rather than just their insertion order. Each item is assigned a priority value, and the queue automatically maintains the items in priority order.
Priority Queue Visualization:
Entry → [P:5][P:3][P:2][P:1] → Exit
⬇️ ⬆️
High Priority Low Priority
items here processed last
Timeline: [1 second per tick]
Calls: ⬇️(P:2) ⬇️(P:5) ⬇️(P:1) ⬇️(P:3)
Queue: [2] [5,2] [5,2,1] [3,2,1] [2,1] [1] []
Processed: 5 - 3 2 1
Priority Queue Visualization:
Entry → [P:5][P:3][P:2][P:1] → Exit
⬇️ ⬆️
High Priority Low Priority
items here processed last
Timeline: [1 second per tick]
Calls: ⬇️(P:2) ⬇️(P:5) ⬇️(P:1) ⬇️(P:3)
Queue: [2] [5,2] [5,2,1] [3,2,1] [2,1] [1] []
Processed: 5 - 3 2 1
Priority queues are essential for:
const priorityQueue = new Queuer<number>({
getPriority: (n) => n // Higher numbers have priority
})
priorityQueue.addItem(1) // [1]
priorityQueue.addItem(3) // [3, 1]
priorityQueue.addItem(2) // [3, 2, 1]
// Processes: 3, 2, then 1
const priorityQueue = new Queuer<number>({
getPriority: (n) => n // Higher numbers have priority
})
priorityQueue.addItem(1) // [1]
priorityQueue.addItem(3) // [3, 1]
priorityQueue.addItem(2) // [3, 2, 1]
// Processes: 3, 2, then 1
The Queuer class supports starting and stopping processing through the start() and stop() methods, and can be configured to start automatically with the started option:
const queue = new Queuer<number>({
wait: 1000,
started: false // Start paused
})
// Control processing
queue.start() // Begin processing items
queue.stop() // Pause processing
// Check processing state
console.log(queue.isRunning()) // Whether the queue is currently processing
console.log(queue.isIdle()) // Whether the queue is running but empty
const queue = new Queuer<number>({
wait: 1000,
started: false // Start paused
})
// Control processing
queue.start() // Begin processing items
queue.stop() // Pause processing
// Check processing state
console.log(queue.isRunning()) // Whether the queue is currently processing
console.log(queue.isIdle()) // Whether the queue is running but empty
If you are using a framework adapter where the queuer options are reactive, you can set the started option to a conditional value:
const queue = useQueuer(
processItem,
{
wait: 1000,
started: isOnline // Start/stop based on connection status IF using a framework adapter that supports reactive options
}
)
const queue = useQueuer(
processItem,
{
wait: 1000,
started: isOnline // Start/stop based on connection status IF using a framework adapter that supports reactive options
}
)
The Queuer provides several helpful methods for queue management:
// Queue inspection
queue.peek() // View next item without removing it
queue.size() // Get current queue size
queue.isEmpty() // Check if queue is empty
queue.isFull() // Check if queue has reached maxSize
queue.getAllItems() // Get copy of all queued items
// Queue manipulation
queue.clear() // Remove all items
queue.reset() // Reset to initial state
queue.getExecutionCount() // Get number of processed items
// Event handling
queue.onUpdate((item) => {
console.log('Processed:', item)
})
// Queue inspection
queue.peek() // View next item without removing it
queue.size() // Get current queue size
queue.isEmpty() // Check if queue is empty
queue.isFull() // Check if queue has reached maxSize
queue.getAllItems() // Get copy of all queued items
// Queue manipulation
queue.clear() // Remove all items
queue.reset() // Reset to initial state
queue.getExecutionCount() // Get number of processed items
// Event handling
queue.onUpdate((item) => {
console.log('Processed:', item)
})
For handling asynchronous operations with multiple workers, see the Async Queueing Guide which covers the AsyncQueuer class.
Each framework adapter builds convenient hooks and functions around the queuer classes. Hooks like useQueuer or useQueueState are small wrappers that can cut down on the boilerplate needed in your own code for some common use cases.
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.