All files / utils delay.ts

95.65% Statements 22/23
75% Branches 3/4
85.71% Functions 6/7
95.45% Lines 21/22

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                          1x   10x   10x 10x   10x 1x     10x 1x     1x   1x 1x       1x 1x 1x 1x 1x 1x   1x     1x 1x     1x      
/**
* Creates a function that batches calls and delays execution.
* It ensures the 'targetFunction' is only called after a 'delayMs' wait period
* AND after the previous execution has finished.
*
* Batch and Delay, collects multiple requests into a single group
* and delays the execution of the target function.
*
* @param targetFunction the promise-returning function to delay,
*        targetFunction should take in a single array
* @param delayMs the number of milliseconds to delay
* @returns delayedFunc that takes an optional argument (array, single value, or nothing)
*/
export function delay<T>(targetFunction: (arg: T[]) => Promise<unknown>, delayMs: number) {
  let context: any;
  let itemsInQueue: T[] = [];
 
  let ongoingWorkPromise: Promise<unknown> = Promise.resolve();
  let currentScheduledBatch: Promise<unknown> | null = null;
 
  const sleep = (ms: number) => new Promise<void>((resolve) => {
    setTimeout(resolve, ms);
  });
 
  return function (this: any, newItem?: T | T[]): Promise<unknown> {
    context = this;
 
    // Add new item to queue when called
    Iif (Array.isArray(newItem)) {
      itemsInQueue = itemsInQueue.concat(newItem);
    } else if (newItem) {
      itemsInQueue.push(newItem);
    }
 
    // Schedule a new batch if there is none scheduled
    if (currentScheduledBatch === null) {
      const batchPromise = Promise.all([sleep(delayMs), ongoingWorkPromise])
        .then(async () => {
          currentScheduledBatch = null;
          const itemsToProcess = [...itemsInQueue];
          itemsInQueue = [];
 
          return targetFunction.apply(context, [itemsToProcess]);
        });
 
      currentScheduledBatch = batchPromise;
      ongoingWorkPromise = batchPromise.catch(() => {});
    }
 
    return currentScheduledBatch;
  };
}