Control and observe
Start and stop the queue, retry and cancel jobs, wait for it to go idle, read its state and listen to everything that happens.
Start, stop, dispose
start()andstop()are idempotent. Callingstart()twice does nothing the second time.stop()does not abort running jobs. Useremove(id)for that.- A stopped queue still accepts
enqueue(). Jobs are saved and wait. dispose()is for tests and hot reload. Stored jobs are untouched.
Good reasons to stop a queue: the user signed out, a remote kill switch, or a circuit breaker after a server outage (see the recipe).
retry, remove, clear
| Call | On a pending job | On an active job | On a failed job |
|---|---|---|---|
retry(id) | returns false | returns false | requeued, attempts reset, deadline cleared |
remove(id) | deleted | aborted (ctx.signal fires), then deleted | deleted |
remove() deletes the job but cannot stop a native task that is already
running. Cancel the native task yourself as well. See
Native tasks.
flush and drain
flush() re-reads connectivity and starts everything due right now. Call it
when the app returns to the foreground, because timers and network events may have been
missed while it was suspended. flushOnForeground does that for you:
drain({ timeoutMs }) flushes, then waits until the queue has nothing left it
can do right now. It resolves true when idle and false if the
timeout ran out first.
drain does not wait for jobs that cannot run right now:
- jobs blocked because the device is offline,
- jobs with no registered handler,
- jobs detached to a native task,
- with
timeoutMs: jobs whose start time (a delay or backoff) is after the timeout.
It is made for background tasks, and it is also the easiest
way to write tests: await queue.drain() then assert (see
Testing).
Reading state
getJobs
Without a filter, the same array is returned until something changes, so it is safe as a React snapshot. Filtered results are new arrays each call.
getStats
Also referentially stable until something changes.
subscribe
The listener runs after any change to any job, with no arguments. It is exactly the
contract useSyncExternalStore needs, which is how the
React hooks are built.
Events
Six moments in a job's life emit an event.
| Event | When | Extra |
|---|---|---|
enqueued | A job was added. | |
started | An attempt began. | |
completed | The handler succeeded and the job was deleted. Also fired by complete(id) for detached jobs. | durationMs |
retry | An attempt failed and another is scheduled. | error, durationMs |
failed | The job gave up. | error, durationMs |
expired | A pending job outlived its ttlMs. | |
removed | Deleted by remove(), clear() or retention. | |
requeued | Connectivity dropped mid-attempt; the job went back to pending without using an attempt. |
Not every transition emits one: an offline requeue (connectivity lost mid-run) is silent,
and remove() emits nothing.
A queue created with the default autoStart: true processes saved jobs
immediately, so events from that first pass (a job that expired while the app was closed,
interrupted jobs starting again) fire before you can attach a listener. To see them, create
the queue with autoStart: false, attach your on / observe
listeners, then call queue.start().
on: one event
observe: every event, with duration
durationMs is measured for attempts run in this process. It is missing for
events that are not an attempt end, and for detached jobs finished with
complete() or fail().
An error thrown by a listener or observer is swallowed so it can never break the queue. Do your own try/catch if you need to know about it.
Connecting analytics and crash tools
observe maps directly onto any analytics SDK. Nothing in QueueKit depends on
them: you attach the one you use.
Payloads may hold personal data. Send ids and types to analytics, not whole payloads.
If you send analytics through the queue, do not enqueue a new analytics job from inside an observer for every event, or each job will create another.
Simple metrics
Debugging
A job that never runs is almost always one of: no handler registered, offline with
requiresNetwork, a rate or concurrency limit, a future runAt, or a
stopped queue. The checklist in the FAQ walks
through each.