Last active
March 6, 2021 16:23
-
-
Save kaisermann/dc8d5798d607ab83b414ca842f19d37d to your computer and use it in GitHub Desktop.
Promised debounced methods with fixed and dynamic intervals
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable @typescript-eslint/no-explicit-any */ | |
type Reject = (reason?: any) => void | |
type Executor<T> = (val: T | PromiseLike<T>) => void | |
export interface ControlledPromise<T> extends Promise<T> { | |
reject: Reject | |
resolve: Executor<T> | |
} | |
function noop() {} | |
/** | |
* Returns a promise with two additional methods: | |
* 'resolve' and 'reject' | |
*/ | |
export function getControlledPromise<T = unknown>() { | |
let resolve: Executor<T> = noop | |
let reject: Reject = noop | |
const bag = new Promise((res, rej) => { | |
resolve = res | |
reject = rej | |
}) as ControlledPromise<T> | |
bag.resolve = resolve | |
bag.reject = reject | |
return bag as ControlledPromise<T> | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { ControlledPromise } from './getControlledPromise' | |
import { getControlledPromise } from './getControlledPromise' | |
type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
type DebounceableFunction = (...params: any[]) => any | |
type DebounceResult<T> = { | |
debounced?: true | |
data?: T | |
} | |
type DebouncedFn<T extends DebounceableFunction> = ( | |
...args: Parameters<T> | |
) => ControlledPromise<DebounceResult<Awaited<ReturnType<T>>>> | |
type DebounceIntervalSetter = (n: number) => void | |
/** | |
* Creates a debounced function and a interval setter. | |
* const [debouncedFn, setInterval] = controlledDebounceFn(fn) | |
*/ | |
function controlledDebounceFn<T extends DebounceableFunction>( | |
fn: T, | |
): [DebouncedFn<T>, DebounceIntervalSetter] { | |
let promise: ControlledPromise<DebounceResult<Awaited<ReturnType<T>>>> | |
let timerId: number | |
let interval: number | |
function debounce(...args: Parameters<T>) { | |
// clear | |
clearTimeout(timerId) | |
promise?.resolve({ debounced: true }) | |
// recreate | |
promise = getControlledPromise() | |
timerId = window.setTimeout(async () => { | |
Promise.resolve(fn(...args)) | |
.then((data) => promise.resolve({ data })) | |
.catch((e) => promise.reject(e)) | |
}, interval) | |
return promise | |
} | |
function setDebounceInterval(n: number) { | |
interval = n | |
} | |
return [debounce, setDebounceInterval] | |
} | |
/** | |
* Creates a debounced function that awaits the specified interval before being executed. | |
*/ | |
export function debounceFn<T extends DebounceableFunction>( | |
fn: T, | |
interval: number, | |
) { | |
const [debounce, setDebounceInterval] = controlledDebounceFn(fn) | |
setDebounceInterval(interval) | |
return debounce | |
} | |
/** | |
* Creates a debounced function with a dynamic interval. | |
* The interval is derived from the args passed onto the debounced function. | |
* | |
* const debouncedFn = derivedDebounceFn(fn, (searchTerm) => { | |
* searchTerm.length < 3 ? 1000 : 300) | |
* }) | |
* | |
* debouncedFn('Ca') // debounces with 1000 | |
* debouncedFn('Cat') // debounces with 300 | |
*/ | |
export function derivedDebounceFn<T extends DebounceableFunction>( | |
fn: T, | |
getInterval: (...args: Parameters<T>) => number, | |
) { | |
const [debounce, setDebounceInterval] = controlledDebounceFn(fn) | |
return (...args: Parameters<T>) => { | |
setDebounceInterval(getInterval(...args)) | |
return debounce(...args) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment