unlimited-timeout
Drop-in setTimeout and setInterval replacements that work correctly with delays longer than JavaScript's ~24.8-day maximum.
Repository Health
Technical Analysis
unlimited-timeout is a tiny, dependency-free package that fixes a well-known JavaScript quirk: setTimeout and setInterval silently misbehave once a delay exceeds 2,147,483,647 milliseconds (about 24.8 days), firing immediately instead of waiting for the intended duration. This package provides drop-in replacements that transparently chunk arbitrarily long delays into a sequence of native timers, so a callback scheduled 30, 90, or 365 days out actually fires when expected.
Built by prolific open-source maintainer Sindre Sorhus, the library preserves familiar native semantics — argument forwarding, ref()/unref() for Node.js event-loop control, and Infinity as a valid “never fire” delay — while adding overflow-safe, drift-corrected scheduling backed by a monotonic clock.
What You Get
- Drop-in setTimeout/clearTimeout replacements with unlimited delay support
- Drop-in setInterval/clearInterval replacements with unlimited delay support
- Node.js ref()/unref() timer handle methods for event-loop control
- An exported MAX_TIMEOUT constant exposing the native 2^31-1 ms ceiling
- Full hand-authored TypeScript type definitions
Common Use Cases
- Scheduling monthly or quarterly recurring jobs directly with setInterval instead of a cron-like library
- Firing a one-time reminder or cleanup callback weeks or months in the future
- Building subscription or trial-expiration timers that span longer than 24.8 days
- Long-running server processes that need to wait out multi-week cooldowns without external schedulers
Under The Hood
Architecture
A single ESM module implements setTimeout/clearTimeout/setInterval/clearInterval via closures over a brand symbol used to validate timer handles. A recursive schedule() closure chunks delays greater than MAX_TIMEOUT into successive native setTimeout calls, chaining them until the remaining delay fits within the native ceiling, at which point it fires the real callback; the setInterval variant re-schedules its next tick before invoking the callback so a thrown error in user code doesn’t kill the interval loop. Target timestamps are tracked with performance.now() to avoid drift or overshoot when a chunk fires late, and returned handle objects expose ref()/unref() that proxy to the underlying native timer for Node.js event-loop control.
Tech Stack Plain ESM JavaScript (type: module) targeting Node.js >=20, distributed with hand-authored TypeScript definitions (index.d.ts) rather than a compiled build step. It has zero runtime dependencies — devDependencies are limited to @types/node, xo (linting), and tsd (type-definition testing) — and uses globalThis.setTimeout/clearTimeout internally so the same code runs in both Node.js and browsers. CI (GitHub Actions) runs the test suite across Node 20 and 24.
Code Quality An 857-line test.js suite covers basic scheduling, argument passing, clearing before and after firing, zero/negative/NaN delay clamping, Infinity handling, ref/unref behavior, multi-chunk long-delay scheduling, drift correction, repeated-clear safety, and interval callback-throw resilience — an unusually thorough suite for a roughly 190-line implementation. Invalid callback arguments throw an explicit TypeError rather than failing silently, naming follows the xo linter’s conventions, and index.test-d.ts adds tsd-driven type assertions on top of the hand-written .d.ts file.
API Design The API deliberately mirrors the native setTimeout/setInterval/clearTimeout/clearInterval signatures so it functions as a true drop-in replacement — same argument order and forwarding, same coercion/clamping semantics for invalid delays, and Infinity treated as “never fire” consistent with native timer overflow behavior. The one intentional deviation — returned handles aren’t interchangeable with native timer IDs — is called out explicitly in the README. Zero configuration and a single import are all that’s required to adopt it.