All files / async / delay.ts

100.00% Branches 11/11
100.00% Lines 43/43
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x17
x74
x74
x130
x186
x195
x195
x186
x186
x227
x227
x186
x186
x558
x186
x189
x189
x189
x191
x192
x192
x192
 
x192
x192
x189
x130
x74
 
x17
 
x73
x73
x73
 
 
x73
x73
x73
 
x73
x131
x131
x131
x131
x73
 
x73
 
x219
x73

































































































// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.

/** Options for {@linkcode delay}. */
export interface DelayOptions {
  /** Signal used to abort the delay. */
  signal?: AbortSignal;
  /** Indicates whether the process should continue to run as long as the timer exists.
   *
   * @default {true}
   */
  persistent?: boolean;
}

// Make type available in browser environments; we catch the `ReferenceError` below
declare const Deno: { unrefTimer(id: number): void };

/**
 * Resolve a {@linkcode Promise} after a given amount of milliseconds.
 *
 * @throws {DOMException} If the optional signal is aborted before the delay
 * duration, and `signal.reason` is undefined.
 * @param ms Duration in milliseconds for how long the delay should last.
 * @param options Additional options.
 *
 * @example Basic usage
 * ```ts no-assert
 * import { delay } from "@std/async/delay";
 *
 * // ...
 * const delayedPromise = delay(100);
 * const result = await delayedPromise;
 * // ...
 * ```
 *
 * @example Disable persistence
 *
 * Setting `persistent` to `false` will allow the process to continue to run as
 * long as the timer exists.
 *
 * ```ts no-assert ignore
 * import { delay } from "@std/async/delay";
 *
 * // ...
 * await delay(100, { persistent: false });
 * // ...
 * ```
 */
export function delay(ms: number, options: DelayOptions = {}): Promise<void> {
  const { signal, persistent = true } = options;
  if (signal?.aborted) return Promise.reject(signal.reason);
  return new Promise((resolve, reject) => {
    const abort = () => {
      clearTimeout(+i);
      reject(signal?.reason);
    };
    const done = () => {
      signal?.removeEventListener("abort", abort);
      resolve();
    };
    const i = setArbitraryLengthTimeout(done, ms);
    signal?.addEventListener("abort", abort, { once: true });
    if (persistent === false) {
      try {
        Deno.unrefTimer(+i);
      } catch (error) {
        if (!(error instanceof ReferenceError)) {
          clearTimeout(+i);
          throw error;
        }
        // deno-lint-ignore no-console
        console.error("`persistent` option is only available in Deno");
      }
    }
  });
}

const I32_MAX = 2 ** 31 - 1;

function setArbitraryLengthTimeout(
  callback: () => void,
  delay: number,
): { valueOf(): number } {
  // ensure non-negative integer (but > I32_MAX is OK, even if Infinity)
  let currentDelay = delay = Math.trunc(Math.max(delay, 0) || 0);
  const start = Date.now();
  let timeoutId: number;

  const queueTimeout = () => {
    currentDelay = delay - (Date.now() - start);
    timeoutId = currentDelay > I32_MAX
      ? Number(setTimeout(queueTimeout, I32_MAX))
      : Number(setTimeout(callback, currentDelay));
  };

  queueTimeout();

  return { valueOf: () => timeoutId };
}