Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make debounce work with asynchronous function #6351

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
79 changes: 72 additions & 7 deletions async/debounce.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.

type DebouncableFunc = (...args: any[]) => void | Promise<void>;

/**
* 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<T extends Array<unknown>> {
(...args: T): void;
// deno-lint-ignore no-explicit-any
export interface DebouncedFunction<T extends DebouncableFunc> {
T;
/** Clears the debounce timeout and omits calling the debounced function. */
clear(): void;
/** Clears the debounce timeout and calls the debounced function immediately. */
Expand Down Expand Up @@ -39,24 +42,24 @@ export interface DebouncedFunction<T extends Array<unknown>> {
* // 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<T extends Array<any>>(
fn: (this: DebouncedFunction<T>, ...args: T) => void,
export function debounce<T extends DebouncableFunc>(
fn: T,
wait: number,
): DebouncedFunction<T> {
let timeout: number | null = null;
let flush: (() => void) | null = null;

const debounced: DebouncedFunction<T> = ((...args: T) => {
const debounced: DebouncedFunction<T> = ((...args) => {
debounced.clear();
flush = () => {
debounced.clear();
fn.call(debounced, ...args);
fn(...args);
};
timeout = Number(setTimeout(flush, wait));
}) as DebouncedFunction<T>;
Expand All @@ -79,3 +82,65 @@ export function debounce<T extends Array<any>>(

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<submitSearch> | 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<F extends (...a: any[]) => Promise<any>>(fn: F, wait: number): F {
let timeout: number | undefined;
let pendingPromiseTuple: PromiseWithResolvers<ReturnType<F>> | 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;
}
Loading