Search code examples
javascriptdebouncing

Working on a fetch/axios/debounce project


I just finished most of the tutorials of an online class on Udemy by instructors named Colt Steele / Steven Grider and facing a project confusion.

I am on a project where I was introduced to making a "debounce" function to not execute an API call every time I input a key inside the input bar. However, I am not understanding the logic behind the debounce function (for example, the spread (...args) right after the return and overall the "shield" it offers. I am wondering if someone can help explain this in a clearer manner? Thank you!

const fetchData = async (searchTerm) =>{ //mark the function as async
    const response = await axios.get('http://www.omdbapi.com/',{ //await to get response from async, calls the web portal
        params: {
            apikey: 'xxx',
            s: 'avengers'
        },
    }); 
    console.log(response.data)
};

const input = document.querySelector('input');

const debounce = (func) => {
    let timeoutId;
    return (...args) => {
        if(timeoutId){
            clearTimeout(timeoutId);
        }
        timeoutId = setTimeout(()=>{
            func.apply(null, args);
        },1000)
    };
};

const onInput = event =>{
    fetchData(event.target.value);
}

input.addEventListener('input', debounce(onInput))

Solution

  • I explain your code step by step, if you like to see it in codesanbox here is a link.

    const input = document.querySelector("input");
    
    /*High Order Function
    a higher-order function is a function that does at least one of the following:
    
    * takes one or more functions as arguments (i.e. procedural parameters),
    * returns a function as its result.
    
    Closure
    Here you will see info about that js topic 
    https://levelup.gitconnected.com/using-closures-in-javascript-to-create-private-variables-c0b358757fe0
    */
    
    // this is a high order function
    const debounce = func => {
      /* this variable save the setTimeout id and the reason why it 
      will be persist is because this is the this is a closure and for that, 
      the returned function will remember their execution context 
      and therefore the value of timeoutId and calledNTimes*/
      let timeoutId;
      let calledNTimes = 0;
    
      /* this args parameter comes from the function passed as 
      argument to debounce, and their value is the event from the addEventListener
      function, you can call args as you like but normaly you will 
      find it with this name.
    
    
      you use the spread operator to the argument from addEventListener as an
      array, because func.apply accept an array of args */
      return (...args) => {
        console.log((calledNTimes += 1), timeoutId);
        console.log("args", args);
    
        /* After the first debounce called, the setTimeout will be 
        clear while the user is typing, or until it is executed */
        if (timeoutId) {
          clearTimeout(timeoutId);
        }
        timeoutId = setTimeout(() => {
          /* with func apply method you execute the function with 
          the args comming from the original call, and this args
          value is the event from the addEventListener */
    
          func.apply(null, args);
        }, 1000);
      };
    };
    
    const onInput = event => {
      fetchData(event.target.value);
    };
    
    /* some interesting thing is that as you are executing debounce here 
    (because you add the parentesis), you can see the second parameter of
    addEventListener as the anonymous function returned by debounce, I mean
    to say:
    
    (...args) => {
        if (timeoutId) {
          clearTimeout(timeoutId);
        }
        timeoutId = setTimeout(() => {
          func.apply(null, args);
        }, 1000);
    
    but if you pass literally the function, you will lose the closure feature.
    */
    
    input.addEventListener("input", debounce(onInput));
    
    /* if you console.log the params with and without the spread operator you will 
    see the difference.*/
    function testFunction(...params) {
      console.log(params);
    }
    
    input.addEventListener("input", testFunction);