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 task data, typed events, coded errors (TaskTimerError), and silentErrors. What's changed →
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.
- 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.
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(), and setImmediate falls back to setTimeout off-Node.
consttimer=newTaskTimer(1000);// base interval: 1000 mstimer.add(task=>console.log(`Run #${task.currentRuns}`)).start();consttimer=newTaskTimer(5000);timer.on(Event.TICK,()=>console.log(`Tick #${timer.tickCount}`));timer.start();consttimer=newTaskTimer(1000);// 1s base resolutiontimer.add([{id: 'task-1',tickInterval: 5,// every 5 ticks → 5stotalRuns: 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 runtickInterval: 10,// every 10 ticks → 10stotalRuns: 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();// return a Promisetimer.add(task=>fetch(url).then(handle));// or call done() when finishedtimer.add((task,done)=>{fs.readFile(path,()=>done());});Tip
Set defer: true on a task to defer its callback to the next event-loop turn (via setImmediate) — useful when the task synchronously blocks the event loop without doing I/O. Set lead: true to run a task once immediately on start() (the leading edge), instead of waiting a full interval.
consttimer=newTaskTimer({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();timer.start();timer.pause();// holds all taskstimer.resume();// continues where it left offtimer.stop();// stops; tasks and counters are retainedtimer.reset();// back to idle; tasks removed silently- 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.
options is either an ITaskTimerOptions object or a number (the base interval in ms).
| 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). |
| 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.
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). |
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.
{ interval?, precision?, stopOnCompleted?, silentErrors? }
{ id?, enabled?, tickDelay?, tickInterval?, totalRuns?, startDate?, stopDate?, defer?, lead?, removeOnCompleted?, data?, callback }
{ started, stopped, elapsed } — timestamps and elapsed time in ms.
{ name, timer, task?, error? }
(task: Task<TData>, done?: () => void) => void | Promise<unknown>
Full reference: onury.io/tasktimer.
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.
- 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.
© 2026, Onur Yıldırım. MIT License.