Skip to content

Repository files navigation

TaskTimer

buildcoveragemutation scoreversiondownloadszero dependenciesESMTSlicensedocumentation

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 →

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 Promise or use the done() 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 data to a task — typed via Task<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 EventEmitter surface (on / once / off / emit …) — listeners get a typed event.
  • Coded errors — every throw is a TaskTimerError with a stable err.code; opt out of swallowing task errors with silentErrors.
  • ESM-only, zero runtime dependencies, runs in Node and the browser, written in TypeScript.

Installation

npm i tasktimer
import{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.

Usage

Simplest example

consttimer=newTaskTimer(1000);// base interval: 1000 mstimer.add(task=>console.log(`Run #${task.currentRuns}`)).start();

A plain timer (events only, no tasks)

consttimer=newTaskTimer(5000);timer.on(Event.TICK,()=>console.log(`Tick #${timer.tickCount}`));timer.start();

Multiple tasks on a single timer

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();

Async tasks

// 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.

Auto-stop when everything completes

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();

Pause and resume

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

How it works

  • You create a timer with a base interval (e.g. 1000 ms) — 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

PropertyTypeDescription
intervalnumberBase tick interval in ms (read/write).
precisionbooleanWhether drift auto-correction is enabled (read/write).
stopOnCompletedbooleanAuto-stop once all tasks complete (read/write).
silentErrorsbooleanSwallow task errors with no taskError listener; false surfaces them (read/write).
stateStateCurrent timer state (read-only).
timeITimeInfo{ started, stopped, elapsed } for the current run (read-only).
tickCountnumberTicks elapsed in the current run (read-only).
taskCountnumberNumber of tasks (read-only).
tasksTask[]All tasks, in insertion order (read-only).
taskRunCountnumberTotal task executions (read-only).
runCountnumberTotal timer runs, including resumes (read-only).

Timer methods

MethodReturnsDescription
add(task)TaskTimerAdd a task, options, callback, or an array of these.
get(id)Task | undefinedGet a task by id (undefined if absent).
remove(task)TaskTimerRemove a task by id or instance.
start()TaskTimerStart (or restart) the timer.
pause()TaskTimerPause the timer and all tasks.
resume()TaskTimerResume a paused timer (starts it if idle).
stop()TaskTimerStop the timer, retaining tasks and counters.
reset()TaskTimerStop 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).

MemberTypeDescription
idstringUnique task id (read-only).
enabledbooleanWhile false, the task bypasses its callback (read/write).
tickDelaynumberTicks to wait before the first run (read/write).
tickIntervalnumberTick interval the task runs on (read/write).
totalRunsnumber | nullRun limit; 0/null = unlimited (read/write).
deferbooleanDefer the callback to the next event-loop turn via setImmediate (read/write).
leadbooleanRun once immediately on start() (the leading edge) (read/write).
removeOnCompletedbooleanRemove the task once completed (read/write).
dataTDataArbitrary user data attached to the task (read/write).
currentRunsnumberNumber of times run so far (read-only).
completedbooleanWhether the task is completed (read-only).
timeITimeInfoThe task's lifetime { started, stopped, elapsed } (read-only).
callbackTaskCallbackThe callback executed on each run (read-only).
reset(options?)TaskReset the run count, optionally re-configuring (id can't change).

Enumerations

All are named exports: import { State, Event, ErrorCode } from 'tasktimer'.

StateIDLE · 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:

EventValueEmitted when
TICKtickEach tick of the timer.
STARTEDstartedThe timer is started.
RESUMEDresumedThe timer is resumed.
PAUSEDpausedThe timer is paused.
STOPPEDstoppedThe timer is stopped.
RESETresetThe timer is reset.
TASKtaskA task is executed.
TASK_ADDEDtaskAddedA task is added.
TASK_REMOVEDtaskRemovedA task is removed.
TASK_COMPLETEDtaskCompletedA task completes its runs / reaches its stopDate.
TASK_ERRORtaskErrorA task throws or rejects.
COMPLETEDcompletedEvery 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.

About

An accurate timer utility for running periodic tasks on the given interval ticks or dates.

Topics

Resources

Stars

131 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages