Skip to main content

Debounce returns a function, that, as long as it continues to be invoked, will not be triggered.

/**
 * Debounce
 *
 * 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.
 *
 * @example
 *
 *   var myEfficientFn = debounce(function() {
 *       // All the taxing stuff you do
 *   }, 250);
 *
 *   window.addEventListener('resize', myEfficientFn);
 *
 * @param  {Function} func The function to invoke.
 * @param  {Number} wait time after it stops being called for N milliseconds.
 * @param  {Boolean} Immediately trigger the function on the leading edge,
 * instead of the trailing.
 *
 * @return {Function}
 */
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);
    }
  };
}

// -------------------------------------
// More advanced version from https://github.com/component/debounce
// -------------------------------------

/**
 * 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. The function also has a property 'clear' that
 * is a function which will clear the timer to prevent previously scheduled
 * executions.
 *
 * @api public
 * @source underscore.js
 * @see http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
 *
 * @param {Function} function to wrap
 * @param {Number} timeout in ms (`100`)
 * @param {Boolean} whether to execute at the beginning (`false`)
 *
 * @example
 *   var debounce = require('debounce');
 *
 *   window.onresize = debounce(resize, 200);
 *
 *   function resize(e) {
 *     console.log('height', window.innerHeight);
 *     console.log('width', window.innerWidth);
 *   }
 *
 *   // To later clear the timer and cancel currently scheduled executions:
 *   window.onresize.clear();
 *
 *   // To execute any pending invocations and reset the timer:
 *   window.onresize.flush();
 */
function debounce(func, wait, immediate) {
  var timeout, args, context, timestamp, result;
  if (null == wait) wait = 100;

  function later() {
    var last = Date.now() - timestamp;

    if (last < wait && last >= 0) {
      timeout = setTimeout(later, wait - last);
    } else {
      timeout = null;
      if (!immediate) {
        result = func.apply(context, args);
        context = args = null;
      }
    }
  }

  var debounced = function () {
    context = this;
    args = arguments;
    timestamp = Date.now();
    var callNow = immediate && !timeout;
    if (!timeout) timeout = setTimeout(later, wait);
    if (callNow) {
      result = func.apply(context, args);
      context = args = null;
    }

    return result;
  };

  debounced.clear = function () {
    if (timeout) {
      clearTimeout(timeout);
      timeout = null;
    }
  };

  debounced.flush = function () {
    if (timeout) {
      result = func.apply(context, args);
      context = args = null;

      clearTimeout(timeout);
      timeout = null;
    }
  };

  return debounced;
}

// Adds compatibility for ES modules
debounce.debounce = debounce;

module.exports = debounce;