DEV Community

Ramu Narasinga
Ramu Narasinga

Posted on • Edited on • Originally published at thinkthroo.com

asyncDebounce function in Refine codebase, a React framework.

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; }; 
Enter fullscreen mode Exit fullscreen mode

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; }; 
Enter fullscreen mode Exit fullscreen mode

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.

Image description

asyncDebounce function definition

export const asyncDebounce = <T extends (...args: any[]) => any>( func: T, wait = 1000, cancelReason?: string, ): DebouncedFunction<T> => { 
Enter fullscreen mode Exit fullscreen mode

This asyncDebounce function accepts three parameters:

  • func

  • wait

  • cancelReason

     

let callbacks: Array<Callbacks<T>> = []; const cancelPrevious = () => { callbacks.forEach((cb) => cb.reject?.(cancelReason)); callbacks = []; }; 
Enter fullscreen mode Exit fullscreen mode

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); 
Enter fullscreen mode Exit fullscreen mode

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); }); }; 
Enter fullscreen mode Exit fullscreen mode

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; 
Enter fullscreen mode Exit fullscreen mode

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

References:

  1. https://github.com/refinedev/refine/blob/main/packages/core/src/definitions/helpers/async-debounce/index.ts

Top comments (0)