diff --git a/async/debounce.ts b/async/debounce.ts index f2299f3dfb2d..77b81c5af923 100644 --- a/async/debounce.ts +++ b/async/debounce.ts @@ -1,13 +1,16 @@ // Copyright 2018-2025 the Deno authors. MIT license. // This module is browser compatible. +type DebouncableFunc = (...args: any[]) => void | Promise; + /** * A debounced function that will be delayed by a given `wait` * time in milliseconds. If the method is called again before * the timeout expires, the previous call will be aborted. */ -export interface DebouncedFunction> { - (...args: T): void; +// deno-lint-ignore no-explicit-any +export interface DebouncedFunction { + T; /** Clears the debounce timeout and omits calling the debounced function. */ clear(): void; /** Clears the debounce timeout and calls the debounced function immediately. */ @@ -39,24 +42,24 @@ export interface DebouncedFunction> { * // output: Function debounced after 200ms with baz * ``` * - * @typeParam T The arguments of the provided function. + * @typeParam T The provided function. * @param fn The function to debounce. * @param wait The time in milliseconds to delay the function. * @returns The debounced function. */ // deno-lint-ignore no-explicit-any -export function debounce>( - fn: (this: DebouncedFunction, ...args: T) => void, +export function debounce( + fn: T, wait: number, ): DebouncedFunction { let timeout: number | null = null; let flush: (() => void) | null = null; - const debounced: DebouncedFunction = ((...args: T) => { + const debounced: DebouncedFunction = ((...args) => { debounced.clear(); flush = () => { debounced.clear(); - fn.call(debounced, ...args); + fn(...args); }; timeout = Number(setTimeout(flush, wait)); }) as DebouncedFunction; @@ -79,3 +82,65 @@ export function debounce>( return debounced; } + +/** + * Creates a debounced async 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 is aborted. + * + * @example Usage + * ```ts ignore + * import { debounceAsync } from "@std/async/debounce"; + * + * const submitSearch = debounceAsync( + * (term: string) => { + * const req = await fetch(`https://api.github.com/search/issues?q=${term}`); + * return await req.json(); + * }, + * 200, + * ); + * + * let promise: ReturnType | undefined: + * const [promise] = ['d', 'de', 'den', 'deno'].map((query) => { + * promise = submitSearch(query); + * await delay(5); + * }); + * const result = await promise; + * // wait 200ms ... + * // output: Function debounced after 200ms with result of 'deno' query + * ``` + * + * @typeParam F The function to debounce. + * @param fn The function to debounce. + * @param wait The time in milliseconds to delay the function. + * @returns The debounced function. + */ +export function debounceAsync Promise>(fn: F, wait: number): F { + let timeout: number | undefined; + let pendingPromiseTuple: PromiseWithResolvers> | undefined; + + const clear = () => { + if (typeof timeout !== 'number') return; + clearTimeout(timeout); + timeout = undefined; + }; + + return ((...args) => { + const p = pendingPromiseTuple ?? Promise.withResolvers(); + pendingPromiseTuple = p; + const { promise, resolve, reject } = pendingPromiseTuple; + clear(); + const flush = async () => { + clear(); + pendingPromiseTuple = undefined; + try { + const res = await fn(...args); + resolve(res); + } catch (error) { + reject(error); + } + }; + timeout = Number(setTimeout(flush, wait)); + return promise; + }) as F; +}