Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions async/debounce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,22 @@ export interface DebouncedFunction<T extends Array<unknown>> {
readonly pending: boolean;
}

/** Options for {@linkcode debounce}. */
export interface DebounceOptions {
/** An AbortSignal that clears the debounce timeout when aborted. */
signal?: AbortSignal | undefined;
}

/**
* 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.
*
* If an {@linkcode AbortSignal} is provided via `options.signal`, aborting the
* signal clears any pending debounce timeout, equivalent to calling
* {@linkcode DebouncedFunction.clear}.
*
* @example Usage
* ```ts ignore
* import { debounce } from "@std/async/debounce";
Expand All @@ -39,17 +49,39 @@ export interface DebouncedFunction<T extends Array<unknown>> {
* // output: [modify] /path/to/file
* ```
*
* @example With AbortSignal
* ```ts ignore
* import { debounce } from "@std/async/debounce";
*
* const controller = new AbortController();
* const log = debounce(
* (event: Deno.FsEvent) =>
* console.log("[%s] %s", event.kind, event.paths[0]),
* 200,
* { signal: controller.signal },
* );
*
* for await (const event of Deno.watchFs("./")) {
* log(event);
* }
*
* // Abort clears any pending debounce
* controller.abort();
* ```
*
* @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.
* @param options Optional parameters.
* @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,
options?: DebounceOptions,
): DebouncedFunction<T> {
if (!Number.isInteger(wait) || wait < 0) {
throw new RangeError("'wait' must be a positive integer");
Expand Down Expand Up @@ -82,5 +114,11 @@ export function debounce<T extends Array<any>>(
get: () => timeout !== null,
});

const signal = options?.signal;
if (signal) {
signal.throwIfAborted();
signal.addEventListener("abort", () => debounced.clear(), { once: true });
}

return debounced;
}
31 changes: 31 additions & 0 deletions async/debounce_test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2018-2026 the Deno authors. MIT license.
import { assertEquals, assertStrictEquals, assertThrows } from "@std/assert";
import { debounce, type DebouncedFunction } from "./debounce.ts";
import { delay } from "./delay.ts";

Deno.test("debounce() handles called", () => {
let called = 0;
Expand Down Expand Up @@ -128,3 +129,33 @@ Deno.test("debounce() re-invocation after flush re-schedules", () => {
assertEquals(called, 2);
assertEquals(d.pending, false);
});

Deno.test("debounce() abort signal clears pending call", async () => {
let called = 0;
const controller = new AbortController();
const d = debounce(() => called++, 100, { signal: controller.signal });
d();
assertEquals(d.pending, true);
controller.abort();
assertEquals(d.pending, false);
await delay(200);
assertEquals(called, 0);
});

Deno.test("debounce() abort signal after flush does not interfere", () => {
let called = 0;
const controller = new AbortController();
const d = debounce(() => called++, 100, { signal: controller.signal });
d();
d.flush();
assertEquals(called, 1);
controller.abort();
assertEquals(called, 1);
});

Deno.test("debounce() throws if signal is already aborted", () => {
assertThrows(
() => debounce(() => {}, 100, { signal: AbortSignal.abort() }),
DOMException,
);
});
1 change: 0 additions & 1 deletion async/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"./abortable": "./abortable.ts",
"./deadline": "./deadline.ts",
"./debounce": "./debounce.ts",
"./unstable-debounce": "./unstable_debounce.ts",
"./delay": "./delay.ts",
"./mux-async-iterator": "./mux_async_iterator.ts",
"./unstable-mux-async-iterator": "./unstable_mux_async_iterator.ts",
Expand Down
104 changes: 0 additions & 104 deletions async/unstable_debounce.ts

This file was deleted.

111 changes: 0 additions & 111 deletions async/unstable_debounce_test.ts

This file was deleted.

Loading