@nxtedition/timers
v1.1.2
Published
Optimized timer pooling for Node.js. Delays >= 1000ms are batched into a single 500ms-resolution interval, reducing the number of active handles and improving performance when many long-lived timers are in use.
Downloads
947
Maintainers
Keywords
Readme
@nxtedition/timers
Optimized timer pooling for Node.js. Delays >= 1000ms are batched into a single 500ms-resolution interval, reducing the number of active handles and improving performance when many long-lived timers are in use.
Short delays (< 1000ms) fall through to the native setTimeout for full resolution.
Install
npm install @nxtedition/timersUsage
import { setTimeout, clearTimeout } from '@nxtedition/timers'
// Short delays use native setTimeout (full resolution)
const handle = setTimeout(
(data) => {
console.log(data)
},
50,
'hello',
)
// Long delays use pooled timers (500ms resolution, lower overhead)
const pooled = setTimeout(
(data) => {
console.log(data)
},
5000,
'world',
)
// Clear works for both
clearTimeout(handle)
clearTimeout(pooled)Opaque data
The third argument is passed directly to the callback, avoiding closure allocations:
setTimeout(
(ctx) => {
ctx.connection.write(ctx.payload)
},
5000,
{ connection, payload },
)Refreshing timers
Pooled timers (delay >= 1000ms) support .refresh() to reset the delay without creating a new timer:
const handle = setTimeout(
() => {
console.log('idle timeout')
},
30000,
undefined,
)
// On activity, reset the timer
handle.refresh()API
setTimeout(callback, delay, opaque)
Schedule callback(opaque) after delay milliseconds.
delay < 1000— delegates to nativeglobalThis.setTimeoutdelay >= 1000— uses the pooled timer mechanism (500ms resolution)
Returns a handle that can be passed to clearTimeout.
clearTimeout(handle)
Cancel a pending timer. Works with both native and pooled handles. Passing null or undefined is a no-op.
How it works
All pooled timers share a single setTimeout handle that fires every 500ms. On each tick, pending timers are checked and expired ones are fired. A +250ms offset is applied to each deadline to center the quantisation error, so pooled timers fire within ±250ms of the requested delay. This dramatically reduces the number of active OS timer handles when many long-lived timers are in use (e.g. connection idle timeouts, retry delays).
Note: Timers with the same delay are not guaranteed to fire in creation order. The execution order of same-delay timers is nondeterministic.
The internal interval is unref()'d so it does not keep the process alive.
Benchmark
yarn benchmarkResults on Apple M3 Pro / Node.js 25.3.0:
| Benchmark | native setTimeout | pooled setTimeout | speedup | | ------------------------------ | ----------------: | ----------------: | ------: | | create + clear | 250.91 ns | 58.90 ns | 4.3x | | create + clear (GC) | 1.95 µs | 73.99 ns | 26.4x | | batch create + clear 100x (GC) | 41.84 µs | 9.80 µs | 4.3x |
Pooled timers avoid native OS timer handles, which are expensive C++ objects for the GC to trace. The advantage grows under GC pressure — up to 26x faster when GC is forced per iteration.
License
MIT
