Skip to content

Instantly share code, notes, and snippets.

@kaisermann
Last active March 6, 2021 16:23
Show Gist options
  • Save kaisermann/dc8d5798d607ab83b414ca842f19d37d to your computer and use it in GitHub Desktop.
Save kaisermann/dc8d5798d607ab83b414ca842f19d37d to your computer and use it in GitHub Desktop.
Promised debounced methods with fixed and dynamic intervals
/* 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>
}
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