Blog
asyncDebounce function in Refine codebase, a React framework.

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;
};

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/@thinkthroo

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
We use cookies
We use cookies to ensure you get the best experience on our website. For more information on how we use cookies, please see our cookie policy.

By clicking "Accept", you agree to our use of cookies.

Learn more