tasktimer
v4.0.0
Published
An accurate timer utility for running periodic tasks on the given interval ticks or dates.
Maintainers
Readme
This module is ESM 🔆. Please read this.
An accurate timer utility for running periodic tasks on the given interval ticks or dates — with a single timer instance, zero runtime dependencies, and full TypeScript types.
📖 Full documentation & guides: onury.io/tasktimer
[!TIP] v4 is a 2026 modernization — ESM-only, zero-dependency, browser-safe, drift-free precision — that also squashed some long-standing bugs and made API improvements, plus new sugar: leading-edge runs (
lead), typed taskdata, typed events, coded errors (TaskTimerError), andsilentErrors. What's changed →
Why TaskTimer?
Because of the single-threaded, asynchronous nature of JavaScript, each execution takes a slice of CPU time, and the wait before the next one varies with the load. This creates a cumulative latency in naive timers that gradually drifts away from the intended schedule. TaskTimer corrects this drift on every tick, and it lets you run many tasks — each on its own interval, run limit, or date window — from a single timer.
Features
- Precision (on by default): the delay between ticks is auto-adjusted when it drifts due to task/CPU load or clock drift. It uses the monotonic
performance.now()(drift-free, in Node and the browser) and auto-recovers via immediate ticks after a blocking task. - Run or schedule multiple tasks on a single timer instance.
- Sync or async tasks — return a
Promiseor use thedone()callback. - Limit runs per task (
totalRuns), add an initial delay (tickDelay), run on the leading edge (lead), or bind a task to a date window (startDate/stopDate). - Attach arbitrary
datato a task — typed viaTask<TData>. - Add, remove, reset, enable/disable, pause and resume tasks at any time — without recreating the timer.
- Stateful: auto-stop when all tasks complete (
stopOnCompleted); free memory when a task finishes (removeOnCompleted). - A familiar, typed
EventEmittersurface (on/once/off/emit…) — listeners get a typed event. - Coded errors — every throw is a
TaskTimerErrorwith a stableerr.code; opt out of swallowing task errors withsilentErrors. - ESM-only, zero runtime dependencies, runs in Node and the browser, written in TypeScript.
Installation
npm i tasktimerimport { TaskTimer, Event, State } from 'tasktimer';Event, State, Task, TaskTimerError and ErrorCode are all named exports (there is no TaskTimer.Event namespace).
[!NOTE] TaskTimer is ESM-only. It runs in Node and the browser via native ESM or a bundler (Vite, esbuild, Rollup, webpack …) — precision uses the universal
performance.now(), andsetImmediatefalls back tosetTimeoutoff-Node.
Usage
Simplest example
const timer = new TaskTimer(1000); // base interval: 1000 ms
timer.add(task => console.log(`Run #${task.currentRuns}`)).start();A plain timer (events only, no tasks)
const timer = new TaskTimer(5000);
timer.on(Event.TICK, () => console.log(`Tick #${timer.tickCount}`));
timer.start();Multiple tasks on a single timer
const timer = new TaskTimer(1000); // 1s base resolution
timer.add([
{
id: 'task-1',
tickInterval: 5, // every 5 ticks → 5s
totalRuns: 10, // run 10 times only (0 = unlimited)
callback(task) {
console.log(`${task.id} ran ${task.currentRuns} times`);
}
},
{
id: 'task-2',
tickDelay: 1, // wait 1 tick before the first run
tickInterval: 10, // every 10 ticks → 10s
totalRuns: 2,
callback(task) {
console.log(`${task.id} ran ${task.currentRuns} times`);
}
}
]);
timer.on(Event.TICK, () => {
console.log(`tick ${timer.tickCount} · elapsed ${timer.time.elapsed} ms`);
});
timer.start();Async tasks
// return a Promise
timer.add(task => fetch(url).then(handle));
// or call done() when finished
timer.add((task, done) => {
fs.readFile(path, () => done());
});[!TIP] Set
defer: trueon a task to defer its callback to the next event-loop turn (viasetImmediate) — useful when the task synchronously blocks the event loop without doing I/O. Setlead: trueto run a task once immediately onstart()(the leading edge), instead of waiting a full interval.
Auto-stop when everything completes
const timer = new TaskTimer({ interval: 1000, stopOnCompleted: true });
timer.add({ totalRuns: 3, callback: doWork });
timer.add({ totalRuns: 5, callback: doOtherWork });
timer.on(Event.COMPLETED, () => console.log('all tasks done'));
timer.start();Pause and resume
timer.start();
timer.pause(); // holds all tasks
timer.resume(); // continues where it left off
timer.stop(); // stops; tasks and counters are retained
timer.reset(); // back to idle; tasks removed silentlyHow it works
- You create a timer with a base interval (e.g.
1000ms) — the tick resolution shared by all tasks. - You add tasks that run on tick intervals (e.g. every 5th tick), optionally with a run limit, an initial delay, or a start/stop date.
- Beyond task callbacks, you can listen for lifecycle events (
tick,task,completed, …). - Tasks can be added, removed, reset, enabled or disabled at any time; the timer can be paused and resumed — all without recreating it.
API
new TaskTimer(options?)
options is either an ITaskTimerOptions object or a number (the base interval in ms).
Timer properties
| Property | Type | Description |
| --- | --- | --- |
| interval | number | Base tick interval in ms (read/write). |
| precision | boolean | Whether drift auto-correction is enabled (read/write). |
| stopOnCompleted | boolean | Auto-stop once all tasks complete (read/write). |
| silentErrors | boolean | Swallow task errors with no taskError listener; false surfaces them (read/write). |
| state | State | Current timer state (read-only). |
| time | ITimeInfo | { started, stopped, elapsed } for the current run (read-only). |
| tickCount | number | Ticks elapsed in the current run (read-only). |
| taskCount | number | Number of tasks (read-only). |
| tasks | Task[] | All tasks, in insertion order (read-only). |
| taskRunCount | number | Total task executions (read-only). |
| runCount | number | Total timer runs, including resumes (read-only). |
Timer methods
| Method | Returns | Description |
| --- | --- | --- |
| add(task) | TaskTimer | Add a task, options, callback, or an array of these. |
| get(id) | Task \| undefined | Get a task by id (undefined if absent). |
| remove(task) | TaskTimer | Remove a task by id or instance. |
| start() | TaskTimer | Start (or restart) the timer. |
| pause() | TaskTimer | Pause the timer and all tasks. |
| resume() | TaskTimer | Resume a paused timer (starts it if idle). |
| stop() | TaskTimer | Stop the timer, retaining tasks and counters. |
| reset() | TaskTimer | Stop and reset to idle, removing all tasks silently. |
TaskTimer also exposes the EventEmitter surface: on / addListener, once, off / removeListener, removeAllListeners, emit, listeners, listenerCount, eventNames.
new Task(options)
A Task is created implicitly via timer.add(...), or explicitly with the constructor (an ITaskOptions with a required id and callback).
| Member | Type | Description |
| --- | --- | --- |
| id | string | Unique task id (read-only). |
| enabled | boolean | While false, the task bypasses its callback (read/write). |
| tickDelay | number | Ticks to wait before the first run (read/write). |
| tickInterval | number | Tick interval the task runs on (read/write). |
| totalRuns | number \| null | Run limit; 0/null = unlimited (read/write). |
| defer | boolean | Defer the callback to the next event-loop turn via setImmediate (read/write). |
| lead | boolean | Run once immediately on start() (the leading edge) (read/write). |
| removeOnCompleted | boolean | Remove the task once completed (read/write). |
| data | TData | Arbitrary user data attached to the task (read/write). |
| currentRuns | number | Number of times run so far (read-only). |
| completed | boolean | Whether the task is completed (read-only). |
| time | ITimeInfo | The task's lifetime { started, stopped, elapsed } (read-only). |
| callback | TaskCallback | The callback executed on each run (read-only). |
| reset(options?) | Task | Reset the run count, optionally re-configuring (id can't change). |
Enumerations
All are named exports: import { State, Event, ErrorCode } from 'tasktimer'.
State — IDLE · RUNNING · PAUSED · STOPPED.
ErrorCode — the code on a thrown TaskTimerError: NO_TASK_PROVIDED · TASK_ID_REQUIRED · CALLBACK_REQUIRED · DUPLICATE_TASK_ID · NO_SUCH_TASK · INVALID_DATE_RANGE · CANNOT_CHANGE_ID · TASK_ERROR.
Event — the events emitted by the timer:
| Event | Value | Emitted when |
| --- | --- | --- |
| TICK | tick | Each tick of the timer. |
| STARTED | started | The timer is started. |
| RESUMED | resumed | The timer is resumed. |
| PAUSED | paused | The timer is paused. |
| STOPPED | stopped | The timer is stopped. |
| RESET | reset | The timer is reset. |
| TASK | task | A task is executed. |
| TASK_ADDED | taskAdded | A task is added. |
| TASK_REMOVED | taskRemoved | A task is removed. |
| TASK_COMPLETED | taskCompleted | A task completes its runs / reaches its stopDate. |
| TASK_ERROR | taskError | A task throws or rejects. |
| COMPLETED | completed | Every task has completed. |
Event listeners receive a typed ITaskTimerEvent: { name, timer, task?, error? }. The related task is event.task and the timer is event.timer — on every event, including taskError.
Types
ITaskTimerOptions
{ interval?, precision?, stopOnCompleted?, silentErrors? }
ITaskOptions<TData>
{ id?, enabled?, tickDelay?, tickInterval?, totalRuns?, startDate?, stopDate?, defer?, lead?, removeOnCompleted?, data?, callback }
ITimeInfo
{ started, stopped, elapsed } — timestamps and elapsed time in ms.
ITaskTimerEvent
{ name, timer, task?, error? }
TaskCallback<TData>
(task: Task<TData>, done?: () => void) => void | Promise<unknown>
Full reference: onury.io/tasktimer.
Changelog
See CHANGELOG.md. Migrating from v3? See the migration notes — v4 is ESM-only, drops the TaskTimer.Event namespace for named exports, and reshapes the event payload.
Other Projects
- AccessControl — Role and Attribute based Access Control for Node.js.
- Configuard — Turn flat config rows from a database table into a nested, typed configuration object — with
${...}templating and accessor-based (ABAC) filtering. - Notation — Read, modify, and filter the contents of objects and arrays via dot/bracket notation strings or glob patterns.
License
© 2026, Onur Yıldırım. MIT License.
