@nxtedition/timers 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -86,7 +86,7 @@ Cancel a pending timer. Works with both native and pooled handles. Passing `null
86
86
 
87
87
  ## How it works
88
88
 
89
- All pooled timers share a single `setTimeout` handle that fires every 500ms. On each tick, pending timers are checked and expired ones are fired. This means pooled timers have at most ~500ms of jitter but dramatically reduce the number of active OS timer handles when many long-lived timers are in use (e.g. connection idle timeouts, retry delays).
89
+ 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).
90
90
 
91
91
  **Note:** Timers with the same delay are not guaranteed to fire in creation order. The execution order of same-delay timers is nondeterministic.
92
92
 
package/lib/index.js CHANGED
@@ -12,11 +12,18 @@ function dispatch() {
12
12
  while (fastIndex < len) {
13
13
  const timer = fastTimers[fastIndex]
14
14
 
15
- if (timer.state === 0) {
16
- timer.state = fastNow + timer.delay
17
- } else if (timer.state > 0 && fastNow >= timer.state) {
15
+ if (timer.state > 0 && fastNow >= timer.state) {
18
16
  timer.state = -1
19
- timer.callback(timer.opaque)
17
+ try {
18
+ timer.callback(timer.opaque)
19
+ } catch (err) {
20
+ // Isolate the throw: rethrow asynchronously so dispatch continues.
21
+ // Otherwise fastIndex stays mid-loop and every prior timer gets
22
+ // delayed by a full tick on the next onTimeout().
23
+ queueMicrotask(() => {
24
+ throw err
25
+ })
26
+ }
20
27
  }
21
28
 
22
29
  if (timer.state === -1) {
@@ -40,6 +47,9 @@ function dispatch() {
40
47
 
41
48
  if (fastTimers.length > 0) {
42
49
  refreshTimeout()
50
+ } else if (fastNowTimeout) {
51
+ globalThis.clearTimeout(fastNowTimeout)
52
+ fastNowTimeout = null
43
53
  }
44
54
  }
45
55
 
@@ -64,7 +74,6 @@ class Timeout {
64
74
 
65
75
  // -2 not in timer list
66
76
  // -1 in timer list but inactive
67
- // 0 in timer list waiting for time
68
77
  // > 0 in timer list waiting for time to expire
69
78
  state = -2
70
79
 
@@ -83,7 +92,11 @@ class Timeout {
83
92
  }
84
93
  }
85
94
 
86
- this.state = 0
95
+ // Offset by half the tick interval to center the timing error.
96
+ // fastNow only advances every 500ms so the actual fire time can
97
+ // deviate from the requested delay. Without this offset the error
98
+ // is up to -500ms (early); with it the worst case is ±250ms.
99
+ this.state = fastNow + this.delay + 250
87
100
  }
88
101
 
89
102
  clear() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/timers",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "type": "module",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -22,7 +22,9 @@
22
22
  "benchmark": "node --experimental-strip-types src/index.bench.ts"
23
23
  },
24
24
  "devDependencies": {
25
+ "@sinonjs/fake-timers": "^15.3.2",
25
26
  "@types/node": "^25.5.0",
27
+ "@types/sinonjs__fake-timers": "^15.0.1",
26
28
  "amaroc": "^1.0.1",
27
29
  "mitata": "^1.0.34",
28
30
  "oxlint-tsgolint": "^0.17.0",
@@ -32,5 +34,5 @@
32
34
  "dependencies": {
33
35
  "@nxtedition/yield": "^1.0.16"
34
36
  },
35
- "gitHead": "a95ef1b72677b853fd7943f7071c266f1789e134"
37
+ "gitHead": "472a4cdfd11ff9f1a7dcf8b81599bc1d9270bc90"
36
38
  }
@@ -1 +0,0 @@
1
- export {};