All files / async / debounce.ts

100.00% Branches 8/8
100.00% Functions 6/6
100.00% Lines 31/31
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x7
x7
x7
 
x13
x4
x4
x9
x9
 
x9
x18
x18
x7
x7
x18
x18
x9
 
x9
x27
x18
x18
x18
x18
x9
 
x9
x9
x9
 
x9
x9
x9
 
x9
x13




















































































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

/**
 * A debounced function whose execution is delayed by a given `wait`
 * time in milliseconds. If the function is called again before
 * the timeout expires, the previous call will be aborted.
 */
export interface DebouncedFunction<T extends Array<unknown>> {
  (...args: T): void;
  /** Clears the debounce timeout and omits calling the debounced function. */
  clear(): void;
  /** Clears the debounce timeout and calls the debounced function immediately. */
  flush(): void;
  /** Returns a boolean whether a debounce call is pending or not. */
  readonly pending: boolean;
}

/**
 * Creates a debounced function that delays the given `func`
 * by a given `wait` time in milliseconds. If the method is called
 * again before the timeout expires, the previous call will be
 * aborted.
 *
 * @example Usage
 * ```ts ignore
 * import { debounce } from "@std/async/debounce";
 *
 * const log = debounce(
 *   (event: Deno.FsEvent) =>
 *     console.log("[%s] %s", event.kind, event.paths[0]),
 *   200,
 * );
 *
 * for await (const event of Deno.watchFs("./")) {
 *   log(event);
 * }
 * // wait 200ms ...
 * // output: [modify] /path/to/file
 * ```
 *
 * @typeParam T The arguments of the provided function.
 * @param fn The function to debounce.
 * @param wait The time in milliseconds to delay the function.
 * Must be a positive integer.
 * @throws {RangeError} If `wait` is not a non-negative integer.
 * @returns The debounced function.
 */
// deno-lint-ignore no-explicit-any
export function debounce<T extends Array<any>>(
  fn: (this: DebouncedFunction<T>, ...args: T) => void,
  wait: number,
): DebouncedFunction<T> {
  if (!Number.isInteger(wait) || wait < 0) {
    throw new RangeError("'wait' must be a positive integer");
  }
  let timeout: number | null = null;
  let pendingFlush: (() => void) | null = null;

  const debounced: DebouncedFunction<T> = ((...args: T) => {
    debounced.clear();
    pendingFlush = () => {
      debounced.clear();
      fn.call(debounced, ...args);
    };
    timeout = Number(setTimeout(pendingFlush, wait));
  }) as DebouncedFunction<T>;

  debounced.clear = () => {
    if (timeout !== null) {
      clearTimeout(timeout);
      timeout = null;
      pendingFlush = null;
    }
  };

  debounced.flush = () => {
    pendingFlush?.();
  };

  Object.defineProperty(debounced, "pending", {
    get: () => timeout !== null,
  });

  return debounced;
}