Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
timers.js424 linesDownload Raw Back to util
1'use strict'
2
3/**
4 * This module offers an optimized timer implementation designed for scenarios
5 * where high precision is not critical.
6 *
7 * The timer achieves faster performance by using a low-resolution approach,
8 * with an accuracy target of within 500ms. This makes it particularly useful
9 * for timers with delays of 1 second or more, where exact timing is less
10 * crucial.
11 *
12 * It's important to note that Node.js timers are inherently imprecise, as
13 * delays can occur due to the event loop being blocked by other operations.
14 * Consequently, timers may trigger later than their scheduled time.
15 */
16
17/**
18 * The fastNow variable contains the internal fast timer clock value.
19 *
20 * @type {number}
21 */
22let fastNow = 0
23
24/**
25 * RESOLUTION_MS represents the target resolution time in milliseconds.
26 *
27 * @type {number}
28 * @default 1000
29 */
30const RESOLUTION_MS = 1e3
31
32/**
33 * TICK_MS defines the desired interval in milliseconds between each tick.
34 * The target value is set to half the resolution time, minus 1 ms, to account
35 * for potential event loop overhead.
36 *
37 * @type {number}
38 * @default 499
39 */
40const TICK_MS = (RESOLUTION_MS >> 1) - 1
41
42/**
43 * fastNowTimeout is a Node.js timer used to manage and process
44 * the FastTimers stored in the `fastTimers` array.
45 *
46 * @type {NodeJS.Timeout}
47 */
48let fastNowTimeout
49
50/**
51 * The kFastTimer symbol is used to identify FastTimer instances.
52 *
53 * @type {Symbol}
54 */
55const kFastTimer = Symbol('kFastTimer')
56
57/**
58 * The fastTimers array contains all active FastTimers.
59 *
60 * @type {FastTimer[]}
61 */
62const fastTimers = []
63
64/**
65 * These constants represent the various states of a FastTimer.
66 */
67
68/**
69 * The `NOT_IN_LIST` constant indicates that the FastTimer is not included
70 * in the `fastTimers` array. Timers with this status will not be processed
71 * during the next tick by the `onTick` function.
72 *
73 * A FastTimer can be re-added to the `fastTimers` array by invoking the
74 * `refresh` method on the FastTimer instance.
75 *
76 * @type {-2}
77 */
78const NOT_IN_LIST = -2
79
80/**
81 * The `TO_BE_CLEARED` constant indicates that the FastTimer is scheduled
82 * for removal from the `fastTimers` array. A FastTimer in this state will
83 * be removed in the next tick by the `onTick` function and will no longer
84 * be processed.
85 *
86 * This status is also set when the `clear` method is called on the FastTimer instance.
87 *
88 * @type {-1}
89 */
90const TO_BE_CLEARED = -1
91
92/**
93 * The `PENDING` constant signifies that the FastTimer is awaiting processing
94 * in the next tick by the `onTick` function. Timers with this status will have
95 * their `_idleStart` value set and their status updated to `ACTIVE` in the next tick.
96 *
97 * @type {0}
98 */
99const PENDING = 0
100
101/**
102 * The `ACTIVE` constant indicates that the FastTimer is active and waiting
103 * for its timer to expire. During the next tick, the `onTick` function will
104 * check if the timer has expired, and if so, it will execute the associated callback.
105 *
106 * @type {1}
107 */
108const ACTIVE = 1
109
110/**
111 * The onTick function processes the fastTimers array.
112 *
113 * @returns {void}
114 */
115function onTick () {
116  /**
117   * Increment the fastNow value by the TICK_MS value, despite the actual time
118   * that has passed since the last tick. This approach ensures independence
119   * from the system clock and delays caused by a blocked event loop.
120   *
121   * @type {number}
122   */
123  fastNow += TICK_MS
124
125  /**
126   * The `idx` variable is used to iterate over the `fastTimers` array.
127   * Expired timers are removed by replacing them with the last element in the array.
128   * Consequently, `idx` is only incremented when the current element is not removed.
129   *
130   * @type {number}
131   */
132  let idx = 0
133
134  /**
135   * The len variable will contain the length of the fastTimers array
136   * and will be decremented when a FastTimer should be removed from the
137   * fastTimers array.
138   *
139   * @type {number}
140   */
141  let len = fastTimers.length
142
143  while (idx < len) {
144    /**
145     * @type {FastTimer}
146     */
147    const timer = fastTimers[idx]
148
149    // If the timer is in the ACTIVE state and the timer has expired, it will
150    // be processed in the next tick.
151    if (timer._state === PENDING) {
152      // Set the _idleStart value to the fastNow value minus the TICK_MS value
153      // to account for the time the timer was in the PENDING state.
154      timer._idleStart = fastNow - TICK_MS
155      timer._state = ACTIVE
156    } else if (
157      timer._state === ACTIVE &&
158      fastNow >= timer._idleStart + timer._idleTimeout
159    ) {
160      timer._state = TO_BE_CLEARED
161      timer._idleStart = -1
162      timer._onTimeout(timer._timerArg)
163    }
164
165    if (timer._state === TO_BE_CLEARED) {
166      timer._state = NOT_IN_LIST
167
168      // Move the last element to the current index and decrement len if it is
169      // not the only element in the array.
170      if (--len !== 0) {
171        fastTimers[idx] = fastTimers[len]
172      }
173    } else {
174      ++idx
175    }
176  }
177
178  // Set the length of the fastTimers array to the new length and thus
179  // removing the excess FastTimers elements from the array.
180  fastTimers.length = len
181
182  // If there are still active FastTimers in the array, refresh the Timer.
183  // If there are no active FastTimers, the timer will be refreshed again
184  // when a new FastTimer is instantiated.
185  if (fastTimers.length !== 0) {
186    refreshTimeout()
187  }
188}
189
190function refreshTimeout () {
191  // If the fastNowTimeout is already set, refresh it.
192  if (fastNowTimeout) {
193    fastNowTimeout.refresh()
194  // fastNowTimeout is not instantiated yet, create a new Timer.
195  } else {
196    clearTimeout(fastNowTimeout)
197    fastNowTimeout = setTimeout(onTick, TICK_MS)
198
199    // If the Timer has an unref method, call it to allow the process to exit if
200    // there are no other active handles.
201    if (fastNowTimeout.unref) {
202      fastNowTimeout.unref()
203    }
204  }
205}
206
207/**
208 * The `FastTimer` class is a data structure designed to store and manage
209 * timer information.
210 */
211class FastTimer {
212  [kFastTimer] = true
213
214  /**
215   * The state of the timer, which can be one of the following:
216   * - NOT_IN_LIST (-2)
217   * - TO_BE_CLEARED (-1)
218   * - PENDING (0)
219   * - ACTIVE (1)
220   *
221   * @type {-2|-1|0|1}
222   * @private
223   */
224  _state = NOT_IN_LIST
225
226  /**
227   * The number of milliseconds to wait before calling the callback.
228   *
229   * @type {number}
230   * @private
231   */
232  _idleTimeout = -1
233
234  /**
235   * The time in milliseconds when the timer was started. This value is used to
236   * calculate when the timer should expire.
237   *
238   * @type {number}
239   * @default -1
240   * @private
241   */
242  _idleStart = -1
243
244  /**
245   * The function to be executed when the timer expires.
246   * @type {Function}
247   * @private
248   */
249  _onTimeout
250
251  /**
252   * The argument to be passed to the callback when the timer expires.
253   *
254   * @type {*}
255   * @private
256   */
257  _timerArg
258
259  /**
260   * @constructor
261   * @param {Function} callback A function to be executed after the timer
262   * expires.
263   * @param {number} delay The time, in milliseconds that the timer should wait
264   * before the specified function or code is executed.
265   * @param {*} arg
266   */
267  constructor (callback, delay, arg) {
268    this._onTimeout = callback
269    this._idleTimeout = delay
270    this._timerArg = arg
271
272    this.refresh()
273  }
274
275  /**
276   * Sets the timer's start time to the current time, and reschedules the timer
277   * to call its callback at the previously specified duration adjusted to the
278   * current time.
279   * Using this on a timer that has already called its callback will reactivate
280   * the timer.
281   *
282   * @returns {void}
283   */
284  refresh () {
285    // In the special case that the timer is not in the list of active timers,
286    // add it back to the array to be processed in the next tick by the onTick
287    // function.
288    if (this._state === NOT_IN_LIST) {
289      fastTimers.push(this)
290    }
291
292    // If the timer is the only active timer, refresh the fastNowTimeout for
293    // better resolution.
294    if (!fastNowTimeout || fastTimers.length === 1) {
295      refreshTimeout()
296    }
297
298    // Setting the state to PENDING will cause the timer to be reset in the
299    // next tick by the onTick function.
300    this._state = PENDING
301  }
302
303  /**
304   * The `clear` method cancels the timer, preventing it from executing.
305   *
306   * @returns {void}
307   * @private
308   */
309  clear () {
310    // Set the state to TO_BE_CLEARED to mark the timer for removal in the next
311    // tick by the onTick function.
312    this._state = TO_BE_CLEARED
313
314    // Reset the _idleStart value to -1 to indicate that the timer is no longer
315    // active.
316    this._idleStart = -1
317  }
318}
319
320/**
321 * This module exports a setTimeout and clearTimeout function that can be
322 * used as a drop-in replacement for the native functions.
323 */
324module.exports = {
325  /**
326   * The setTimeout() method sets a timer which executes a function once the
327   * timer expires.
328   * @param {Function} callback A function to be executed after the timer
329   * expires.
330   * @param {number} delay The time, in milliseconds that the timer should
331   * wait before the specified function or code is executed.
332   * @param {*} [arg] An optional argument to be passed to the callback function
333   * when the timer expires.
334   * @returns {NodeJS.Timeout|FastTimer}
335   */
336  setTimeout (callback, delay, arg) {
337    // If the delay is less than or equal to the RESOLUTION_MS value return a
338    // native Node.js Timer instance.
339    return delay <= RESOLUTION_MS
340      ? setTimeout(callback, delay, arg)
341      : new FastTimer(callback, delay, arg)
342  },
343  /**
344   * The clearTimeout method cancels an instantiated Timer previously created
345   * by calling setTimeout.
346   *
347   * @param {NodeJS.Timeout|FastTimer} timeout
348   */
349  clearTimeout (timeout) {
350    // If the timeout is a FastTimer, call its own clear method.
351    if (timeout[kFastTimer]) {
352      /**
353       * @type {FastTimer}
354       */
355      timeout.clear()
356      // Otherwise it is an instance of a native NodeJS.Timeout, so call the
357      // Node.js native clearTimeout function.
358    } else {
359      clearTimeout(timeout)
360    }
361  },
362  /**
363   * The setFastTimeout() method sets a fastTimer which executes a function once
364   * the timer expires.
365   * @param {Function} callback A function to be executed after the timer
366   * expires.
367   * @param {number} delay The time, in milliseconds that the timer should
368   * wait before the specified function or code is executed.
369   * @param {*} [arg] An optional argument to be passed to the callback function
370   * when the timer expires.
371   * @returns {FastTimer}
372   */
373  setFastTimeout (callback, delay, arg) {
374    return new FastTimer(callback, delay, arg)
375  },
376  /**
377   * The clearTimeout method cancels an instantiated FastTimer previously
378   * created by calling setFastTimeout.
379   *
380   * @param {FastTimer} timeout
381   */
382  clearFastTimeout (timeout) {
383    timeout.clear()
384  },
385  /**
386   * The now method returns the value of the internal fast timer clock.
387   *
388   * @returns {number}
389   */
390  now () {
391    return fastNow
392  },
393  /**
394   * Trigger the onTick function to process the fastTimers array.
395   * Exported for testing purposes only.
396   * Marking as deprecated to discourage any use outside of testing.
397   * @deprecated
398   * @param {number} [delay=0] The delay in milliseconds to add to the now value.
399   */
400  tick (delay = 0) {
401    fastNow += delay - RESOLUTION_MS + 1
402    onTick()
403    onTick()
404  },
405  /**
406   * Reset FastTimers.
407   * Exported for testing purposes only.
408   * Marking as deprecated to discourage any use outside of testing.
409   * @deprecated
410   */
411  reset () {
412    fastNow = 0
413    fastTimers.length = 0
414    clearTimeout(fastNowTimeout)
415    fastNowTimeout = null
416  },
417  /**
418   * Exporting for testing purposes only.
419   * Marking as deprecated to discourage any use outside of testing.
420   * @deprecated
421   */
422  kFastTimer
423}
424 
codekingpro/portable-devtools · Team Ai