Flow Control
Taskora provides three flow control strategies — debounce, throttle, and deduplicate — all implemented as atomic Lua scripts.
Debounce
Replace the previous pending job for the same key. Only the last dispatch within the delay window is processed.
searchIndexTask.dispatch(data, {
debounce: {
key: `reindex:${documentId}`,
delay: "2s",
},
})Use case: Avoid redundant reindexing when a document is edited multiple times in quick succession. Only the final version is indexed.
How It Works
- First dispatch creates a delayed job
- Subsequent dispatches with the same key replace the previous job (reset the delay timer)
- When the delay elapses without a new dispatch, the job moves to the waiting queue
Throttle
Rate-limit dispatches per key. Excess dispatches are rejected.
const handle = callExternalApiTask.dispatch(data, {
throttle: {
key: "stripe-api",
max: 100, // max 100 dispatches
window: "1m", // per 1 minute window
},
})
if (!handle.enqueued) {
console.log("Rate limited — try again later")
}Use case: Respect external API rate limits by capping how many jobs can be enqueued in a time window.
How It Works
- Each dispatch increments a counter for the key
- Counter resets when the window expires
- If counter exceeds
max, the dispatch is rejected (handle.enqueued = false) - With
throwOnReject: true, throwsThrottledErrorinstead
Deduplicate
Skip dispatch if a job with the same key already exists in a matching state.
const handle = generateReportTask.dispatch(data, {
deduplicate: {
key: `report:${userId}`,
while: ["waiting", "active"], // default: ["waiting", "delayed", "active"]
},
})
if (!handle.enqueued) {
console.log("Report already in progress:", handle.existingId)
}Use case: Prevent duplicate report generation when a user clicks "Generate" multiple times.
How It Works
- Checks if a job with the same dedup key exists in any of the
whilestates - If found, returns the existing job's ID and sets
handle.enqueued = false - If not found, creates a new job and stores the dedup key
- Dedup keys are automatically cleaned up when jobs complete or fail
Throwing on Rejection
By default, throttle and dedup silently reject. Use throwOnReject for explicit error handling:
try {
sendEmailTask.dispatch(data, {
throttle: { key: "emails", max: 100, window: "1h" },
throwOnReject: true,
})
} catch (err) {
if (err instanceof ThrottledError) {
// err.key = "emails"
}
if (err instanceof DuplicateJobError) {
// err.key, err.existingId
}
}Combining Flow Control
Flow control options are mutually exclusive — use only one per dispatch call. Collect tasks are also mutually exclusive with all flow control options.