coalesced
v1.0.1
Published
Lightweight async task coalescer. Prevents overlapping execution and coalesces multiple calls into at most one rerun.
Maintainers
Readme
Coalesced Runner
Lightweight async task coalescing with deterministic execution. Prevents race conditions by ensuring tasks run sequentially and coalesce repeated calls.
Features
- No race conditions — never runs the same task concurrently
- Coalescing — multiple calls collapse into a single rerun
- Event-driven — lifecycle events (
start,success,error,drain) - Fail-fast by default — errors crash if unhandled
- Key-based isolation — separate execution per key
- Memory-safe — no function retention, automatic cleanup
Installation
npm install coalesced-runnerQuick Start
import { CoalescedRunner } from "coalesced-runner";
const runner = new CoalescedRunner();
const task = async () => {
console.log("run");
};
runner.run(task);
runner.run(task);
runner.run(task);Output
run
runMultiple calls are coalesced into:
- 1 execution immediately
- 1 rerun after completion (if triggered while running)
How It Works
run() ──▶ [RUNNING]
│
├─ run() → mark pending
├─ run() → still pending (no queue)
│
▼
[DONE]
│
└─ pending? → run once moreWhy this exists
In async systems, this pattern is common:
await updateState();
await updateState();
await updateState();Without control, this causes:
- race conditions
- duplicated work
- inconsistent state
CoalescedRunner guarantees:
- only one task runs at a time
- repeated calls are merged into at most one rerun
Key-based Execution
runner.runWithKey("user:1", async () => {
// runs independently from other keys
});Each key has its own isolated queue.
Events
The runner is event-driven using a familiar EventEmitter API.
Available Events
| Event | Description |
| --------- | --------------------------- |
| start | Task execution started |
| success | Task completed successfully |
| error | Task failed |
| drain | No more pending tasks (per fn/key) |
Example
import { RunnerEvents } from "coalesced-runner";
runner.on(RunnerEvents.ERROR, (err) => {
console.error(err.meta, err.cause);
});
runner.on(RunnerEvents.DRAIN, (meta) => {
console.log("idle");
});Meta (Typed Context)
Each event receives a meta object describing the task.
type RunnerMeta =
| { type: "fn"; name?: string }
| { type: "key"; key: string | symbol; name?: string };Example
runner.on(RunnerEvents.START, (meta) => {
if (meta.type === "key") {
console.log(meta.key);
}
});Error Handling Behavior
- If an
errorlistener exists → error is emitted - If no listener → error is thrown asynchronously (fail-fast)
API
runner.run(fn)
Run using function identity (===).
const fn = async () => {};
runner.run(fn);
runner.run(fn); // coalescedrunner.runWithKey(key, fn)
Run using explicit key.
runner.runWithKey("user:1", async () => {
await syncUser(1);
});Key type
type RunnerKey = string | symbol;runner.clearKey(key)
Manually clear internal state.
runner.clearKey("user:1");Important Notes
1. This is NOT a queue
run()
run()
run()Not: run 3 times Actually: run 2 times
2. Function identity matters
runner.run(async () => {});
runner.run(async () => {});NOT coalesced (different functions)
3. Use keys for dynamic tasks
runner.runWithKey(`user:${id}`, fn);4. Does NOT fix race inside your function
const fn = async () => {
// your logic must still be safe
};Runner only controls execution timing, not data correctness.
5. Fire-and-forget
runner.run(fn);- does NOT return a Promise
- errors handled via event
Example: Prevent Spam
const save = async () => {
await api.save();
};
button.onclick = () => {
runner.run(save);
};spam click → no overload
Example: Sync Latest State
runner.runWithKey("sync", async () => {
await syncState();
});ensures:
- no overlap
- always runs latest update
When to Use
Use this when you need:
- prevent overlapping async tasks
- avoid queue buildup
- ensure latest update is applied
- handle burst events safely
When NOT to Use
- need strict ordering → use queue
- need concurrency → use semaphore
- need dedupe only → use single-flight
Comparison
| Pattern | Behavior | | -------------------- | ---------------- | | Queue | runs all tasks | | Debounce | delay + collapse | | Throttle | limit rate | | Single-flight | run once only | | Coalesced Runner | run + 1 rerun |
License
MIT
