DEV Community

Cover image for JS Polyfills - Part 4 Debounce & Throttle - Leading & Trailing options
Sriya
Sriya

Posted on • Updated on

JS Polyfills - Part 4 Debounce & Throttle - Leading & Trailing options


Github Code: JS Polyfills
What are Debounce and throttle ? They are techniques for optimizing browser performance in JS.
Why we use them ? These are for limiting the rate of function execution/API request
Where can we use them ? Input/button/window events, Searching/
Typeahead, Save as user inputs, hover actions, interactions while scrolling

11. Basic Debounce()

/**
   * @param {Function} customBasicDebounce
   * @param {Function} func
   * @param {number} millisec delay
   */

//What are we doing here? 
//1. Set the context
//2. If the timer exist, then clear the timer before calling the function
//3. If not, call the function
function customBasicDebounce(func, delay = 400) {
  //initalizing timer here - closure 
  let timer;
  return ((...args) => {
    //clearing timer before calling again for restricting multiple instances of timer
    clearTimeout(timer);
    //calls the function after delay
    timer = setTimeout(() => func.apply(this, ...args), delay);
  });
}
Enter fullscreen mode Exit fullscreen mode

12. Debounce with leading & trailing options

  • Function: debounce(callback, delay, option={leading:false, trailing: true})
  • Description: In short, If leading -> Capture initial click after delay, if trailing -> Capture latest click after a delay
  • Polyfill JS, HTML, Usecase result: Debounce Leading & Trailing
/**
   * @param {Function} func
   * @param {number} delay
   * @param {boolean} option.leading
   * @param {boolean} option.trailing
   */
//What are we doing here? 
//1. Set the context and trailing arguments
//2. If not leading/trailing, return null
//3. If timer is done but leading is true, invoke the func execution
//4. If not, save the context for later execution
//5. clear the timer to avoid multiple timer instances 
//6. call the timer if trailing is true n trailing args exists
//7. Reset the timer and args
function customAdvancedDebounce(func, delay, option = { leading: false, trailing: true }) {

  let timer = null; // same like basic debounce
  let trailingArgs = null; // as we require last arguments for trailing 

  if (!option.leading && !option.trailing) return () => null; //if both false, return null

  return function debounced(...args) { //returns a debounced function

    if (!timer && option.leading) { // timer done but leading true
      func.apply(this, args); //call func
    } else {
      trailingArgs = args; // arguments will be the last args
    }

    clearTimeout(timer); //clear timer for avoiding multiple timer instances

    timer = setTimeout(() => {
      if (option.trailing && trailingArgs) func.apply(this, trailingArgs);  // trailingArgs is present and trailing is true

      trailingArgs = null; //reset last arguments
      timer = null; // reset timer
    }, delay);
  }
}
Enter fullscreen mode Exit fullscreen mode

13. Basic Throttle()

/**
   * @param {Function} customBasicThrottle
   * @param {Function} func
   * @param {number} millisec delay
   */
//What are we doing here? 
//1. Set the context and trailing arguments
//2. If not executed before, execute now and update the context
//3. during exe, update the context and if trailing args exist, execute the function
//and reset the context and last args
//4. If executed, save the context for later execution

function customBasicThrottle(func, delay) {
  let lastRan = false, //last time the function got ex ecuted
      trailingArgs = null; //last arguments

  return function (...args) { //returns function
    if (!lastRan) { //if the function didnt get executed before
      func.apply(this, args) // call the function
      lastRan = true; //update the flag
      let timer = () => setTimeout(() => { //while executing 
        lastRan = false; //update the flag
        if (trailingArgs) { // if last arguments exist
          func.apply(this, trailingArgs); //invoke the function with those last arguments
          lastRan = true; //update the flag
          trailingArgs = null; //reset the arguments
          timer(); 
        }
      }, delay);
      timer(); 
    }
    else // if function got executed before
      trailingArgs = args //else update the last arguments with passed arguments
  }
}

Enter fullscreen mode Exit fullscreen mode

14. Throttle with leading and trailing options

  • Function: throttle(callback, delay, option={leading:false, trailing: true})
  • Description: leading/trailing indicates function should be run at the beginning of the stream of events or the end.
  • Polyfill JS, HTML, Usecase result:Throttle with leading and trailing
/**
   * @param {Function} func
   * @param {number} delay
   * @param {boolean} option.leading
   * @param {boolean} option.trailing
   */
//What are we doing here? 
//1. Set the context, timer and trailing arguments
//2. if timer called within cooldown period, update context and save the trailing args for later execution
//3. If leading, execute the function
//4. If trailing, update context and save last args for later execution
//5. set cooldown period
//6. if trailing and the trailing args exist, invoke the timer, reset the cooldown period

const advThrottle = (func, delay, options = { leading: true, trailing: false }) => {
  let timer = null,
    lastRan = null,
    trailingArgs = null;

  return function (...args) {

    if (timer) { //called within cooldown period
      lastRan = this; //update context
      trailingArgs = args; //save for later
      return;
    } 

    if (options.leading) {// if leading
      func.call(this, ...args) //call the 1st instance
    } else { // else it's trailing
      lastRan = this; //update context
      trailingArgs = args; //save for later
    }

    const coolDownPeriodComplete = () => {
      if (options.trailing && trailingArgs) { // if trailing and the trailing args exist
        func.call(lastRan, ...trailingArgs); //invoke the instance with stored context "lastRan"
        lastRan = null; //reset the status of lastRan
        trailingArgs = null; //reset trailing arguments
        timer = setTimeout(coolDownPeriodComplete, delay) //clear the timout
      } else {
        timer = null; // reset timer
      }
    }

    timer = setTimeout(coolDownPeriodComplete, delay);
  }
}
Enter fullscreen mode Exit fullscreen mode

Keep Learning!

Top comments (1)

Collapse
 
bcdbuddy profile image
Babacar Cisse DIA

Bro your code is wrong. Spent the last 20min troubleshooting it. Below what is working for me for anyone passing

function customBasicDebounce(func, delay = 400) {
  let timer;
  return ((...args) => {
    clearTimeout(timer);
    // args is already an array no need to thread it
    timer = setTimeout(() => func.apply(this, args), delay);
  });
}
Enter fullscreen mode Exit fullscreen mode