ordered-coalescing-task-queue
v1.2.0
Published
An Ordered Coalescing Task Queue.
Maintainers
Readme
ordered-coalescing-task-queue
An in-memory task queue in TypeScript that coalesces adjacent tasks, executes work with bounded concurrency, retries failures, handles timeouts, and delivers results in strict FIFO submission order.
https://github.com/user-attachments/assets/a808dbe4-b422-47a3-9768-94dfe53c21c3
Features
- Coalesces adjacent pending and retryable tasks up to a configurable depth.
- Controlled concurrency with
maxConcurrency. - Guarantees FIFO delivery order of results regardless of task completion order.
- Configurable execution retries via execution credits.
- Configurable execution timeout per attempt with
AbortSignal. - Graceful queue clearance with abortion of in-flight executions.
Installation
npm install ordered-coalescing-task-queueQuick Start
import { OrderedCoalescingTaskQueue } from "ordered-coalescing-task-queue";
/*
* Create and configure the queue.
*/
const queue = new OrderedCoalescingTaskQueue({
maxConcurrency: 2,
maxCoalescingDepth: 3,
initialExecutionCredits: 3,
timeoutMs: 5000,
// Provide an executor that will run with the given tasks.
executeTask: async (task, abortSignal) => {
console.log("Executing task:", task.ids, task.payload);
},
// Provide a function to merge payloads of adjacent tasks.
coalesceTaskPayloads: (a, b) => {
/* combine tasks payloads a and b */
},
// Listen for the task final outcome (success or permanent failure) in FIFO order.
onTaskResult: (outcome) => {
console.log("Task result:", outcome);
},
// Optional hook to selectively permit task coalescence (provide null if unused).
canCoalesceTasks: null,
// Optional hook called immediately when an attempt fails (provide null if unused).
onFailedTaskExecutionAttempt: null,
// Optional hook called when coalesceTaskPayloads throws (provide null if unused).
onFailedTaskCoalescence: null,
});
/*
* Use the queue (submitting tasks).
*/
queue.pushTask({ id: "task-1", payload: { message: "Hello, world!" } });
// queue.clearAllTasks() will abort all tasks and clear the queueUsage
import {
OrderedCoalescingTaskQueue,
type IQueueConfig,
} from "ordered-coalescing-task-queue";
type TaskId = string;
type TaskPayload = { items: string[] };
type TaskResult = { processedCount: number };
const config: IQueueConfig<TaskId, TaskPayload, TaskResult> = {
maxConcurrency: 2,
maxCoalescingDepth: 5,
initialExecutionCredits: 3,
timeoutMs: 5000,
coalesceTaskPayloads(oldestPayload, newestPayload) {
return {
items: [...oldestPayload.items, ...newestPayload.items],
};
},
async executeTask(task, abortSignal) {
// task.ids contains all coalesced task IDs in order
// abortSignal triggers on timeout or queue clearance
const response = await fetch("https://example.com/api/batch", {
method: "POST",
body: JSON.stringify(task.payload),
signal: abortSignal,
});
return (await response.json()) as TaskResult;
},
onTaskResult(outcome) {
if (outcome.status === "succeeded") {
console.log("Tasks succeeded:", outcome.ids, outcome.result);
} else {
console.error("Tasks failed permanently:", outcome.ids, outcome.error);
}
},
canCoalesceTasks: null,
onFailedTaskExecutionAttempt: (outcome) => {
console.warn(
"Attempt failed, retries left:",
outcome.remainingExecutionCredits,
);
},
onFailedTaskCoalescence: (context) => {
console.warn("Could not coalesce tasks:", context.error);
},
};
const queue = new OrderedCoalescingTaskQueue(config);
queue.pushTask({ id: "task-1", payload: { items: ["a"] } });
queue.pushTask({ id: "task-2", payload: { items: ["b"] } });API
Class: OrderedCoalescingTaskQueue<TTaskId, TTaskPayload, TTaskResult>
Implements IQueue<TTaskId, TTaskPayload>.
constructor(config: IQueueConfig<TTaskId, TTaskPayload, TTaskResult>)- Validates and creates a new queue instance.
- Throws an
Errorif numeric bounds (maxConcurrency,maxCoalescingDepth,initialExecutionCredits,timeoutMs) are less than 1 orNaN.
pushTask(task: IAtomicTaskDescription<TTaskId, TTaskPayload>): void- Enqueues an atomic task at the tail of the queue.
- Triggers sequential coalescing and scheduling.
clearAllTasks(): void- Aborts all running tasks immediately.
- Removes all pending and running tasks from the queue.
- Dispatches a failed outcome with
TaskAbortedErrorfor every dropped task toonTaskResult. - Resets internal state so the queue can accept new tasks.
Configuration: IQueueConfig<TTaskId, TTaskPayload, TTaskResult>
Options passed to OrderedCoalescingTaskQueue:
executeTask(task: ICoalescedTaskDescription<TTaskId, TTaskPayload>, abortSignal: AbortSignal): Promise<TTaskResult>- Mandatory function that executes a task.
- Receives the task description (with coalesced IDs and payload) and an
AbortSignal. - The signal aborts when the per-attempt timeout expires or when
clearAllTasks()is called.
coalesceTaskPayloads(oldestTaskPayload: TTaskPayload, newestTaskPayload: TTaskPayload): TTaskPayload- Mandatory function to combine two payloads into one.
- Thrown errors are caught; the two tasks are then kept separate.
canCoalesceTasks: ((oldestTask: ICoalescedTaskDescription<TTaskId, TTaskPayload>, newestTask: ICoalescedTaskDescription<TTaskId, TTaskPayload>) => boolean) | null- Optional predicate function to determine if two adjacent tasks are allowed to coalesce together.
- If provided, adjacent tasks are only coalesced when this function returns
true. - Set to
nullto allow coalescing for all eligible adjacent tasks.
maxConcurrency: number- Maximum number of tasks executed in parallel.
- Must be
>= 1. Set to1for sequential execution, orInfinityfor unbounded concurrency.
maxCoalescingDepth: number- Maximum total number of task IDs allowed in a single coalesced task.
- Must be
>= 1. Set to1to disable coalescing, orInfinityfor unlimited coalescing.
initialExecutionCredits: number- Number of execution attempts granted to each task.
- Must be
>= 1. Set to1for no retries, orInfinityto retry indefinitely.
timeoutMs: number- Execution timeout in milliseconds per attempt.
- Must be
>= 1. Set toInfinityto disable timeout.
onTaskResult: (outcome: ITaskOutcome<TTaskId, TTaskResult>) => void- Mandatory callback invoked when a task definitively finishes (either succeeded, or failed with 0 credits left, or aborted/cleared).
- Guaranteed to be called in the exact order tasks were submitted (FIFO head-of-line delivery).
onFailedTaskExecutionAttempt: ((outcome: IFailedTaskOutcome<TTaskId>) => void) | null- Optional callback invoked immediately in real-time after each failed attempt, whether retryable or final.
- Unlike
onTaskResult, it is not delayed by head-of-line blocking behind slower predecessor tasks. - Triggered by executor rejections and timeouts; not triggered by queue clearance via
clearAllTasks()(reported directly toonTaskResult) or coalescence failures. - Set to
nullto ignore.
onFailedTaskCoalescence: ((context: IFailedCoalescence<TTaskId, TTaskPayload>) => void) | null- Optional callback invoked when
coalesceTaskPayloadsthrows. - Set to
nullto ignore.
- Optional callback invoked when
Task Descriptions
IAtomicTaskDescription<TTaskId, TTaskPayload>- Input object for
pushTask. id: TTaskId: User-defined task identifier.payload: TTaskPayload: Task payload data.
- Input object for
ICoalescedTaskDescription<TTaskId, TTaskPayload>- Object passed to
executeTask. ids: TTaskId[]: Array of task IDs coalesced into this task, in submission order.payload: TTaskPayload: Combined payload data.
- Object passed to
Task Outcomes
ITaskOutcome<TTaskId, TTaskResult>- Union of
ISuccessfulTaskOutcomeandIFailedTaskOutcome.
- Union of
ISuccessfulTaskOutcome<TTaskId, TTaskResult>ids: TTaskId[]: Task IDs associated with the result.status: "succeeded"result: TTaskResult: Output returned byexecuteTask.
IFailedTaskOutcome<TTaskId>ids: TTaskId[]: Task IDs associated with the failure.status: "failed"error: TaskError: Error that caused the failure.remainingExecutionCredits: number: Credits remaining after this attempt.
Error Types
TaskError- Union of
TaskExecutionError | TaskAbortedError | TaskTimedOutError.
- Union of
TaskExecutionError- Extends
Error. - Emitted when
executeTaskthrows or returns a rejected Promise. cause: unknown: The underlying error thrown or rejected by the executor.
- Extends
TaskAbortedError- Extends
Error. - Emitted when a task is aborted due to
clearAllTasks()or signal abortion.
- Extends
TaskTimedOutError- Extends
Error. - Emitted when task execution exceeds
timeoutMs.
- Extends
Additional Types
IFailedCoalescence<TTaskId, TTaskPayload>- Passed to
onFailedTaskCoalescence. oldestTask: ICoalescedTaskDescription<TTaskId, TTaskPayload>: Older task in the pair.newestTask: ICoalescedTaskDescription<TTaskId, TTaskPayload>: Newer task in the pair.error: unknown: Exception thrown bycoalesceTaskPayloads.
- Passed to
IQueue<TTaskId, TTaskPayload>- Interface implemented by
OrderedCoalescingTaskQueue. - Defines
pushTask(task)andclearAllTasks().
- Interface implemented by
Behavior Details
- Coalescence Ordering: Coalescing is applied sequentially from oldest to newest adjacent tasks in the queue.
- Coalescence Error Recovery: If
coalesceTaskPayloadsthrows, the older task is marked uncoalescible to prevent infinite coalescing loops; both tasks remain in the queue and are executed separately. - Result Delivery Order: Results are buffered until all preceding tasks have finished. Even if a later task finishes before an earlier one due to concurrency, outcomes are emitted via
onTaskResultstrictly in submission order. - Attempt Failure Observability: Unlike
onTaskResult,onFailedTaskExecutionAttemptis invoked immediately in real-time as soon as an attempt fails (e.g. error or timeout), providing immediate visibility without waiting for preceding tasks to complete. - Queue Clearance: Calling
clearAllTasks()aborts in-flight attempts and synchronously delivers a terminalTaskAbortedErrorfor all dropped tasks viaonTaskResult, bypassingonFailedTaskExecutionAttempt. - Retries: When an attempt fails and execution credits remain, the task stays in the queue to be retried by the scheduler.
