Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1import { delay } from "./utils.js";
2
3/**
4 * Stores/runs a set of timers and can alert callers when all timers
5 * have finished, and that can be closed by callers.
6 */
7export default class TimerSet {
8 // We have to actually store the timers (eek),
9 // not just a count, to support close functionality.
10 private readonly timers: Set<any> = new Set();
11 private closed = false;
12
13 public setTimeout(
14 cb: (...args: any[]) => void,
15 ms: number,
16 ...args: any[]
17 ): NodeJS.Timeout {
18 if (this.closed) {
19 throw new Error("TimerSet is closed. New timers cannot be added.");
20 }
21
22 const timer = setTimeout(() => {
23 this.timers.delete(timer);
24 cb(...args);
25 }, ms);
26
27 this.timers.add(timer);
28 return timer;
29 }
30
31 public clearTimeout(timeout: NodeJS.Timeout) {
32 this.timers.delete(timeout);
33 clearTimeout(timeout);
34 }
35
36 /**
37 * Stops the set from accepting more timers and returns a promise that
38 * resolves when all known timers are done. If `timeout` ms elapse before
39 * the timers finish, it `unref`s them so they don't block the node process
40 * from closing, and then its returned promise resolves.
41 */
42 public async close(timeout?: number) {
43 this.closed = true;
44
45 // Ironic to use a timer to poll a count of timers, but hey.
46 const timersDone = async (): Promise<void> =>
47 this.timers.size === 0 ? undefined : delay(20).then(timersDone);
48
49 if (timeout === undefined) {
50 return timersDone();
51 }
52
53 const unrefAllAfterTimeout = delay(timeout).then(() => {
54 this.timers.forEach((it) => {
55 it.unref();
56 });
57 });
58
59 await Promise.race([unrefAllAfterTimeout, timersDone()]);
60 }
61}