All files / async / delay.ts

100.00% Branches 13/13
100.00% Lines 47/47
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
100
101
102
103
104
105
106
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x26
x156
x156
x285
x414
x425
x425
x414
x414
x526
x526
x414
x414
x1242
x414
x417
x417
x417
x419
x420
x420
x420
 
x420
x420
x417
x285
x156
 
x26
 
x155
x155
x155
 
 
x155
 
x155
x279
x837
x279
 
x160
x160
 
x160
x167
x167
x167
x167
x160
 
x160
 
x480
x155









































































































// Copyright 2018-2026 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.
 *
 * If the optional signal is aborted before the delay duration, the returned
 * promise rejects with the signal's reason. If no reason is provided to
 * `abort()`, the browser's default `DOMException` with name `"AbortError"` is used.
 *
 * @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)
  delay = Math.trunc(Math.max(delay, 0) || 0);

  if (delay <= I32_MAX) {
    const id = Number(setTimeout(callback, delay));
    return { valueOf: () => id };
  }

  const start = Date.now();
  let timeoutId: number;

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

  queueTimeout();

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