All files / src/utils/async delay.ts

95.23% Statements 20/21
66.66% Branches 4/6
100% Functions 4/4
95% Lines 19/20

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                                                          91x                                                             30x 107x   107x     107x   107x 98x       98x 98x 98x     107x 10x 3x     7x   7x 7x 7x     7x     107x    
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;
 
  // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
  const { promise, resolve, reject } = Promise.withResolvers<void>();
 
  timeoutId = setTimeout(() => {
    Iif (settled) {
      return;
    }
 
    settled = true;
    timeoutId = undefined;
    resolve();
  }, duration);
 
  const cancel = (): void => {
    if (settled) {
      return;
    }
 
    settled = true;
 
    Eif (timeoutId !== undefined) {
      clearTimeout(timeoutId);
      timeoutId = undefined;
    }
 
    reject(new CanceledError());
  };
 
  return { promise, cancel };
};