All files / src/utils/async delay.ts

90.9% Statements 20/22
50% Branches 3/6
100% Functions 5/5
90.47% Lines 19/21

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                                                          88x                                                             29x 99x   99x     99x 99x 99x 92x       92x 92x 92x       99x 5x       5x   5x 5x 5x     5x     99x    
import { CanceledError } from '../common/canceled-error.ts';
import { toNonNegativeInteger } from '../common/duration.ts';
import type { Timeout } from './types.ts';
 
/**
 * Delays the execution of the code for the specified amount of milliseconds.
 *
 * @example
 *
 * ```ts
 * import { delay } from 'emitnlog/utils';
 *
 * // Wait for 500 milliseconds before continuing
 * await delay(500);
 * console.log('This will be logged after 500ms');
 *
 * // Chain multiple operations with delays
 * async function processWithDelays() {
 *   await step1();
 *   await delay(1000); // 1 second cooldown
 *   await step2();
 *   await delay(2000); // 2 second cooldown
 *   await step3();
 * }
 * ```
 *
 * @param milliseconds The amount of milliseconds to wait (0 if negatived, and ceil if decimal).
 * @returns A promise that resolves after the specified amount of milliseconds.
 */
export const delay = (milliseconds: number): Promise<void> => cancelableDelay(milliseconds).promise;
 
export type CancelableDelay = Readonly<{ promise: Promise<void>; cancel: () => void }>;
 
/**
 * Delays the execution of the code for the specified amount of milliseconds, with cancellation support.
 *
 * Invoking the returned `cancel` function causes the promise to reject with a `CanceledError`. This is the only
 * expected case in which the promise can reject.
 *
 * @example
 *
 * ```ts
 * import { CanceledError, cancelableDelay } from 'emitnlog/utils';
 *
 * const { promise, cancel } = cancelableDelay(500);
 * setTimeout(() => cancel(), 250);
 *
 * try {
 *   await promise;
 * } catch (error) {
 *   if (error instanceof CanceledError) {
 *     console.log('Canceled');
 *   }
 * }
 * ```
 *
 * @param milliseconds The amount of milliseconds to wait (0 if negatived, and ceil if decimal).
 * @returns A promise that resolves after the specified amount of milliseconds, and a cancel function that rejects the
 *   promise with `CanceledError` when invoked.
 */
export const cancelableDelay = (milliseconds: number): CancelableDelay => {
  const duration = toNonNegativeInteger(milliseconds);
  let timeoutId: Timeout | undefined;
  let settled = false;
  let rejectDelay: ((error: CanceledError) => void) | undefined;
 
  const promise = new Promise<void>((resolve, reject) => {
    rejectDelay = reject as (error: CanceledError) => void;
    timeoutId = setTimeout(() => {
      Iif (settled) {
        return;
      }
 
      settled = true;
      timeoutId = undefined;
      resolve();
    }, duration);
  });
 
  const cancel = (): void => {
    Iif (settled) {
      return;
    }
 
    settled = true;
 
    Eif (timeoutId !== undefined) {
      clearTimeout(timeoutId);
      timeoutId = undefined;
    }
 
    rejectDelay?.(new CanceledError());
  };
 
  return { promise, cancel };
};