vue-reactive-queue
v0.0.1
Published
A Vue 3 composable for managing async task queues with reactive state, concurrency control, pause/resume, and retry support
Readme
vue-reactive-queue
Reactive async task queue for Vue 3 with concurrency control.
Features
- 🚀 Concurrency Control - Limit parallel task execution
- 📊 Reactive State - Track task status in real-time with Vue reactivity
- ⏸️ Pause/Resume - Control queue execution flow
- 🔄 Retry - Retry failed tasks with one call
- 📝 Task History - Keep track of completed tasks
- 🎯 Manual Execution - Start specific tasks on demand
- 💪 TypeScript - Full type support with generics
Install
npm install vue-reactive-queueUsage
Basic
import { useQueue } from 'vue-reactive-queue'
const queue = useQueue()
// Add tasks to the queue
queue.add(async () => {
await processFile('document.pdf')
})
queue.add(async () => {
await processFile('image.png')
})Concurrency Control
const queue = useQueue({ concurrency: 3 })
// Only 3 tasks will run in parallel
queue.add(() => fetchUser(1))
queue.add(() => fetchUser(2))
queue.add(() => fetchUser(3))
queue.add(() => fetchUser(4)) // waits for a slotReactive State
<script setup>
import { useQueue } from 'vue-reactive-queue'
const queue = useQueue({ concurrency: 2 })
function addTask() {
queue.add(async () => {
await new Promise(r => setTimeout(r, 1000))
return 'done'
})
}
</script>
<template>
<div>
<p>Pending: {{ queue.stats.value.pending }}</p>
<p>Running: {{ queue.stats.value.running }}</p>
<p>Completed: {{ queue.stats.value.completed }}</p>
<button @click="addTask">Add Task</button>
<div v-for="task in queue.tasks.value" :key="task.id">
<span>Task #{{ task.id }}: {{ task.status }}</span>
<button v-if="task.status === 'rejected'" @click="queue.retry(task.id)">
Retry
</button>
</div>
</div>
</template>Pause and Resume
const queue = useQueue()
queue.add(() => task1())
queue.add(() => task2())
queue.pause() // Stop executing new tasks
queue.resume() // Continue executionStart Paused
const queue = useQueue({ immediate: false })
queue.add(() => task1())
queue.add(() => task2())
// Tasks are pending, not running
console.log(queue.stats.value.isPaused) // true
// Start when ready
queue.resume()Manual Task Execution
Start a specific task immediately, ignoring the pause state:
const queue = useQueue({ immediate: false })
const { id: idA } = queue.add(() => taskA())
const { id: idB } = queue.add(() => taskB())
const { id: idC } = queue.add(() => taskC())
// Only execute taskB, leave others pending
await queue.start(idB)Retry Failed Tasks
const queue = useQueue()
const { id } = queue.add(async () => {
await uploadFile(file) // might fail due to network issues
})
// Later, if the task failed
const result = queue.retry(id)
if (result.success) {
await result.promise
}Task Metadata
Attach custom data to tasks for UI rendering:
interface TaskMeta {
filename: string
size: number
}
const queue = useQueue<TaskMeta>()
queue.add(
() => uploadFile(file),
{ filename: file.name, size: file.size }
)<template>
<div v-for="task in queue.tasks.value" :key="task.id">
<span>{{ task.meta?.filename }}</span>
<span>{{ task.status }}</span>
</div>
</template>Callbacks
const queue = useQueue({
onSuccess(task) {
console.log('Task completed:', task.result)
},
onError(task, error) {
console.error('Task failed:', error)
},
onFinished() {
console.log('All tasks done!')
},
})Limit History
// Only keep the last 10 completed tasks
const queue = useQueue({ maxHistory: 10 })
// Keep no history (only pending/running tasks visible)
const queue = useQueue({ maxHistory: 0 })Wait for Idle
const queue = useQueue()
queue.add(() => task1())
queue.add(() => task2())
queue.add(() => task3())
await queue.waitForIdle()
console.log('All tasks completed!')Dynamic Concurrency
import { ref } from 'vue'
const limit = ref(2)
const queue = useQueue({ concurrency: limit })
// Reactively update concurrency
limit.value = 5
// Or use setConcurrency
queue.setConcurrency(10)Type Declarations
export interface QueueTask<T = unknown, M = unknown> {
readonly id: number
status: 'pending' | 'running' | 'fulfilled' | 'rejected'
readonly createdAt: number
startedAt?: number
finishedAt?: number
result?: T
error?: unknown
readonly meta?: M
}
export interface QueueStats {
readonly pending: number
readonly running: number
readonly completed: number
readonly failed: number
readonly total: number
readonly isIdle: boolean
readonly isPaused: boolean
}
export interface UseQueueOptions<M = unknown> {
concurrency?: MaybeRefOrGetter<number> // default: 1
immediate?: boolean // default: true
maxHistory?: number // default: undefined (keep all)
onSuccess?: (task: QueueTask<unknown, M>) => void
onError?: (task: QueueTask<unknown, M>, error: unknown) => void
onFinished?: () => void
}
export interface UseQueueReturn<M = unknown> {
tasks: Readonly<ShallowRef<QueueTask<unknown, M>[]>>
stats: ComputedRef<QueueStats>
concurrency: Readonly<ShallowRef<number>>
add: <T>(fn: () => T | Promise<T>, meta?: M) => { id: number; task: QueueTask<T, M>; promise: Promise<T> }
start: (id: number) => Promise<unknown> | undefined
retry: (id: number) => RetryResult
remove: (id: number) => boolean
clear: () => void
pause: () => void
resume: () => void
setConcurrency: (value: number) => void
waitForIdle: () => Promise<void>
}License
MIT
