-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
111 lines (94 loc) · 2.57 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
'use strict';
class IntervalManager {
#runningCounter = new Counter();
/** @type {NodeJS.Timeout[]} */
#intervals = [];
/** @type {Promise<undefined> | undefined} */
#closingPromise = undefined;
/** @type {number} */
#timeoutMs;
/**
* @param {Object} options
* @param {number=} options.timeoutMs Timeout for `.close()` method. Default value is `60_000` ms.
*/
constructor({ timeoutMs = 60_000 } = {}) {
this.#timeoutMs = timeoutMs;
}
/**
* @param {function(): Promise<unknown> | unknown} callback
* @param {number=} ms
*
* Schedules and registers repeated execution of `callback` every `ms` milliseconds.
*
* When delay is larger than 2147483647 or less than 1, the delay will be set to 1. Non-integer delays are truncated to an integer.
*
* If callback is not a function, a TypeError will be thrown.
*
* If the Interval Manager is in the closing state then doesn't schedule anything.
*/
add(callback, ms) {
if (this.#closingPromise) {
return;
}
const interval = setInterval(async () => {
this.#runningCounter.increase();
try {
await callback();
} finally {
this.#runningCounter.decrease();
}
}, ms);
this.#intervals.push(interval);
}
/**
* Switcher Interval Manager to closing state and clears all registered intervals.
*
* Returns a promise which will resolve as soon as all interval's callbacks will be executed. Supposed to be called in graceful shutdown handler.
*
* Will reject the promise if timeout is reached.
*
* @returns {Promise<undefined>}
* @throws {Error}
*/
close() {
if (this.#closingPromise) {
return this.#closingPromise;
}
this.#intervals.forEach(i => clearInterval(i));
this.#closingPromise = new Promise(async (resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Timeout reached'));
}, this.#timeoutMs);
this.#runningCounter.onValueEqualsZero(() => {
clearInterval(timeout);
resolve(undefined);
});
});
return this.#closingPromise;
}
}
const noop = () => {};
class Counter {
#value = 0;
#onValueEqualsZeroCb = noop;
increase() {
this.#value += 1;
}
decrease() {
this.#value -= 1;
this.#tryOnValueEqualsZero();
}
/**
* @param {function(): void} cb
*/
onValueEqualsZero(cb) {
this.#onValueEqualsZeroCb = cb;
this.#tryOnValueEqualsZero();
}
#tryOnValueEqualsZero() {
if (this.#value === 0) {
this.#onValueEqualsZeroCb();
}
}
}
module.exports = { IntervalManager };