In this article, we will review a function named asyncDebounce in Refine source code.
import debounce from "lodash/debounce"; type Callbacks<T extends (...args: any) => any> = { resolve?: (value: Awaited<ReturnType<T>>) => void; reject?: (reason?: any) => void; }; type DebouncedFunction<T extends (...args: any) => any> = { (...args: Parameters<T>): Promise<Awaited<ReturnType<T>>>; flush: () => void; cancel: () => void; }; /** * Debounces sync and async functions with given wait time. The debounced function returns a promise which can be awaited or catched. * Only the last call of the debounced function will resolve or reject. * Previous calls will be rejected with the given cancelReason. * * The original debounce function doesn't work well with async functions, * It won't return a promise to resolve/reject and therefore it's not possible to await the result. * This will always return a promise to handle and await the result. * Previous calls will be rejected immediately after a new call made. */ export const asyncDebounce = <T extends (...args: any[]) => any>( func: T, wait = 1000, cancelReason?: string, ): DebouncedFunction<T> => { let callbacks: Array<Callbacks<T>> = []; const cancelPrevious = () => { callbacks.forEach((cb) => cb.reject?.(cancelReason)); callbacks = []; }; const debouncedFunc = debounce((...args: Parameters<T>) => { const { resolve, reject } = callbacks.pop() || {}; Promise.resolve(func(...args)) .then(resolve) .catch(reject); }, wait); const runner = (...args: Parameters<T>) => { }; runner.flush = () => debouncedFunc.flush(); runner.cancel = () => { debouncedFunc.cancel(); cancelPrevious(); }; return runner; };
This piece of code is picked from a file named async-debounce/index.ts
Let’s try to understand this code in chunks.
import debounce from "lodash/debounce"; type Callbacks<T extends (...args: any) => any> = { resolve?: (value: Awaited<ReturnType<T>>) => void; reject?: (reason?: any) => void; }; type DebouncedFunction<T extends (...args: any) => any> = { (...args: Parameters<T>): Promise<Awaited<ReturnType<T>>>; flush: () => void; cancel: () => void; };
So Refine still uses debounce from lodash/debounce, evident from that import. There are two generic types written here, Callbacks and DebouncedFunction.
Why would you need asyncDebounce?
*The original debounce function doesn’t work well with async functions,
*It won’t return a promise to resolve/reject and therefore it’s not possible to await the result.
* This will always return a promise to handle and await the result.
* Previous calls will be rejected immediately after a new call made.
This is a comment I picked from async-debounce/index.ts in Refine codebase.
asyncDebounce function definition
export const asyncDebounce = <T extends (...args: any[]) => any>( func: T, wait = 1000, cancelReason?: string, ): DebouncedFunction<T> => {
This asyncDebounce function accepts three parameters:
func
wait
cancelReason
let callbacks: Array<Callbacks<T>> = []; const cancelPrevious = () => { callbacks.forEach((cb) => cb.reject?.(cancelReason)); callbacks = []; };
callbacks is initialized to empty array. cancelPrevious rejects alls the previous requests fired by calling reject with a cancelReason and then set to an empty array.
const debouncedFunc = debounce((...args: Parameters<T>) => { const { resolve, reject } = callbacks.pop() || {}; Promise.resolve(func(...args)) .then(resolve) .catch(reject); }, wait);
callbacks.pop() returns the last item in the array and the Promise.resolve is called on func(…args)
in debouncedFunc.
const runner = (...args: Parameters<T>) => { return new Promise<Awaited<ReturnType<T>>>((resolve, reject) => { cancelPrevious(); callbacks.push({ resolve, reject, }); debouncedFunc(...args); }); };
This is an important function and returns a Promise. cancelPrevious function rejects all the previous promises. callbacks is pushed with an object and then debouncedFunc is called with args.
runner.flush = () => debouncedFunc.flush(); runner.cancel = () => { debouncedFunc.cancel(); cancelPrevious(); }; return runner;
Since runner returns a Promise and asyncDebounce returns runner, this is how asyncDebounce returns a Promise according to the comment mentioned above in this article.
About me:
Hey, my name is Ramu Narasinga. I study large open-source projects and create content about their codebase architecture and best practices, sharing it through articles, videos.
I am open to work on interesting projects. Send me an email at ramu.narasinga@gmail.com
My Github — https://github.com/ramu-narasinga
My website — https://ramunarasinga.com
My Youtube channel — https://www.youtube.com/@ramu-narasinga
Learning platform — https://thinkthroo.com
Codebase Architecture — https://app.thinkthroo.com/architecture
Best practices — https://app.thinkthroo.com/best-practices
Production-grade projects — https://app.thinkthroo.com/production-grade-projects
Top comments (0)