All files / src timeoutLatch.ts

44.44% Statements 20/45
15% Branches 3/20
38.46% Functions 5/13
44.44% Lines 20/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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            1x 1x 1x     1x 1x 1x                                           1x 1x                             1x 1x     1x 1x                     1x 1x                                     1x   1x 1x     1x 1x   1x    
// Setting / Unsetting timeouts for every keystroke was a significant overhead
// Inspired from https://github.com/iostreamer-X/timeout-latch
 
export class TimeoutLatch {
  private timeLeftMS: number;
  private timeoutMS: number;
  private isCancelled = false;
  private isTimeExhausted = false;
  private callbacks: Function[] = [];
 
  constructor(callback: Function, timeoutMS: number) {
    this.timeLeftMS = timeoutMS;
    this.timeoutMS = timeoutMS;
    this.callbacks.push(callback);
  }
 
  tick(): void {
    if (!this.isCancelled && !this.isTimeExhausted) {
      this.timeLeftMS--;
      if (this.timeLeftMS <= 0) {
        this.isTimeExhausted = true;
        const callbacks = this.callbacks.slice();
        this.callbacks.length = 0;
        callbacks.forEach((callback) => {
          try {
            callback();
          } catch (error) {
            console.error('TimeoutLatch callback error:', error);
          }
        });
      }
    }
  }
 
  cancel(): void {
    this.isCancelled = true;
    this.callbacks.length = 0;
  }
 
  reset(): void {
    this.timeLeftMS = this.timeoutMS;
    this.isCancelled = false;
    this.isTimeExhausted = false;
  }
 
  get isDone(): boolean {
    return this.isCancelled || this.isTimeExhausted;
  }
}
 
class Scheduler {
  private interval: NodeJS.Timeout | null = null;
  private latches = new Set<TimeoutLatch>();
 
  add(latch: TimeoutLatch): void {
    this.latches.add(latch);
    this.start();
  }
 
  remove(latch: TimeoutLatch): void {
    this.latches.delete(latch);
    if (this.latches.size === 0) {
      this.stop();
    }
  }
 
  private start(): void {
    Eif (this.interval === null) {
      this.interval = setInterval(() => {
        this.latches.forEach((latch) => {
          latch.tick();
          if (latch.isDone) {
            this.remove(latch);
          }
        });
      }, 1);
    }
  }
 
  private stop(): void {
    if (this.interval !== null) {
      clearInterval(this.interval);
      this.interval = null;
    }
  }
}
 
let globalScheduler: Scheduler | null = null;
 
export const getScheduler = (): Scheduler => {
  Iif (typeof window === 'undefined') {
    return new Scheduler();
  }
  Eif (!globalScheduler) {
    globalScheduler = new Scheduler();
  }
  return globalScheduler;
};