import { delay } from "./utils.js"; /** * Stores/runs a set of timers and can alert callers when all timers * have finished, and that can be closed by callers. */ export default class TimerSet { // We have to actually store the timers (eek), // not just a count, to support close functionality. private readonly timers: Set = new Set(); private closed = false; public setTimeout( cb: (...args: any[]) => void, ms: number, ...args: any[] ): NodeJS.Timeout { if (this.closed) { throw new Error("TimerSet is closed. New timers cannot be added."); } const timer = setTimeout(() => { this.timers.delete(timer); cb(...args); }, ms); this.timers.add(timer); return timer; } public clearTimeout(timeout: NodeJS.Timeout) { this.timers.delete(timeout); clearTimeout(timeout); } /** * Stops the set from accepting more timers and returns a promise that * resolves when all known timers are done. If `timeout` ms elapse before * the timers finish, it `unref`s them so they don't block the node process * from closing, and then its returned promise resolves. */ public async close(timeout?: number) { this.closed = true; // Ironic to use a timer to poll a count of timers, but hey. const timersDone = async (): Promise => this.timers.size === 0 ? undefined : delay(20).then(timersDone); if (timeout === undefined) { return timersDone(); } const unrefAllAfterTimeout = delay(timeout).then(() => { this.timers.forEach((it) => { it.unref(); }); }); await Promise.race([unrefAllAfterTimeout, timersDone()]); } }