ticker.ts

May 12, 2023ยทa simple non-blocking wrapper around setTimeout

interface TickerConfig {
  fn: () => Promise<void>;
  intervalMs?: number;
  firstRunDelay?: number;
}

class Ticker {
  private fn: () => Promise<void>;
  private timer?: NodeJS.Timeout;
  private firstRunDelay = 1000 * 4;
  private intervalMs = 1000 * 15;

  constructor(config: TickerConfig) {
    this.fn = config.fn;
    this.intervalMs = config.intervalMs || this.intervalMs;
    this.firstRunDelay = config.firstRunDelay || this.firstRunDelay;
  }

  start(): void {
    this.scheduleNextExecution(this.firstRunDelay);
  }

  stop(): void {
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = undefined;
    }
  }

  private scheduleNextExecution(delay: number): void {
    const milliseconds = Math.max(delay || 0);
    this.timer = setTimeout(async () => this.execute(), milliseconds);
    // unblock the event loop so if the parent process is finished
    // it may exit.
    this.timer.unref();
  }

  private async execute(): Promise<void> {
    try {
      await this.fn();
    } catch {
      // noop, this insulates from implosion
    } finally {
      this.scheduleNextExecution(this.intervalMs);
    }
  }
}