Skip to content

Instantly share code, notes, and snippets.

@nmsdvid
Created February 4, 2014 16:32
Show Gist options
  • Star 71 You must be signed in to star a gist
  • Fork 17 You must be signed in to fork a gist
  • Save nmsdvid/8807205 to your computer and use it in GitHub Desktop.
Save nmsdvid/8807205 to your computer and use it in GitHub Desktop.
Simple JavaScript Debounce Function
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
}
/* minified */
function debounce(a,b,c){var d;return function(){var e=this,f=arguments;clearTimeout(d),d=setTimeout(function(){d=null,c||a.apply(e,f)},b),c&&!d&&a.apply(e,f)}}
/* usage */
var myEfficientFn = debounce(function() {
// All the taxing stuff you do
}, 250);
window.addEventListener('resize', myEfficientFn);
@casulit
Copy link

casulit commented Jul 6, 2020

If we write this in TS? what would be the type of callback and ...args

@eduardomoroni
Copy link

interface DebouncedFunction {
  (): any;
  cancel: () => void;
}

export const debounce = <F extends (...args: any[]) => ReturnType<F>>(
  func: F,
  wait: number,
  immediate?: boolean
) => {
  let timeout: number = 0;

  const debounced: DebouncedFunction = function(this: void) {
    const context: any = this;
    const args = arguments;

    const later = function() {
      timeout = 0;
      if (!immediate) func.call(context, ...args);
    };
    const callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = window.setTimeout(later, wait);
    if (callNow) func.call(context, ...args);
  };

  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = 0;
  };

  return debounced as (...args: Parameters<F>) => ReturnType<F>;
};

@oznekenzo
Copy link

If found that, when setting immediate to true the debounced function was not invoked at all.
This is because as soon as you get at if (immediate && !timeout) the timeout variable was just set.
Maybe move that if statement to before setting timeout?

This 👆

@nicograef
Copy link

nicograef commented Oct 26, 2021

My TypeScript solution:

function debounce(func: (...args: unknown[]) => unknown, delay = 200) {
  let timeout: number;

  return function (...args: unknown[]) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func(...args), delay);
  };
}

or maybe (also when running in node or web)

function debounce<T>(func: (...args: T[]) => unknown, delay = 200) {
  let timeout: number | NodeJS.Timeout;

  return function (...args: T[]) {
    clearTimeout(timeout as number);
    timeout = setTimeout(() => func(...args), delay);
  };
}

@geoffreyhale
Copy link

geoffreyhale commented Jan 9, 2022

I suggest adding to our debounce function return type unknown or I prefer typeof func as shown on line 4 below:

function debounce<T>(
  func: (...args: T[]) => unknown,
  delay = 200
): typeof func {
  let timeout: number | NodeJS.Timeout;
  return function (...args: T[]) {
    clearTimeout(timeout as number);
    timeout = setTimeout(() => func(...args), delay);
  };
}

Without it results may be faced with type conversion issues a la ts(2352).

@MominBinShahid
Copy link

MominBinShahid commented Feb 13, 2022

As indicated above, in the main gist function, using immediate will break the implementation
So, for such needs where we wanted to call the function immediately (leading edge instead of trailing)
Try this implementation https://youmightnotneed.com/lodash#function

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment