Refactor davidwalsh's debounce function using ES6 arrow and more
Tags: javascript
Published: 2020-03-13 05:58:47


We all know Davidwalsh's debounce function. The post is from 2014 but even now many developers use this and most of the Youtube tutorials are based on this. 
If you are not familiar with Davidwalsh debounce function take a look at it here: 
https://davidwalsh.name/javascript-debounce-function

When I looked at this, it didn't strike me. I have to bang my head many times to understand why he ended up writing the way it is. 

So I ended up refactoring the code using a new ES6 arrow function. 

Our brains are not made the same way, so some people may get my function better and others don't. Important thing is, you understand what you are writing and your team agrees.

That being said. here we go 
```
const debounce = (func, delay) => {
  let timerId; 
  return () => {
    clearTimeout(timerId);
    timerId = setTimeout(func, delay); // start the timer
  };
};
```
That's it??!! Yes! this is the bare minimum version of the debounce. It utilize the [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) to store the `timerId` in the parent scope.
You can try it on the sandbox here: https://codesandbox.io/s/es6-debounce-example-llgu7?file=/src/index.js

Davidwalsh's debounce function has more features built in. 
Now, instead of adding all features to make the function complex, let's isolate the features, so we can better understand how each feature effects the function. At the end we combine both features into single function. 
1. pass arguments to the function
2. execute function immediately then delay


## Argument Version
```
const debounce = (func, delay) => {
  let timerId; // keep track of current timer

  // return the function
  return (...args) => {
    const boundFunc = func.bind(this, ...args);
    clearTimeout(timerId);
    timerId = setTimeout(boundFunc, delay); // start the timer
  };
};
```
This was easy to add. We just needed to bind the arguments to the function.
https://codesandbox.io/s/es6-debounce-arguments-example-2p4bp?file=/src/index.js


## Immediate Version
```
const debounce = (func, delay) => {
  let timerId;
  return () => {
    if (!timerId) {
      func();
    }
    clearTimeout(timerId);
    timerId = setTimeout(() => {
      timerId = null; 
    }, delay);
  };
};
```
As you can see, on the initial call, we execute the function immediately then set the timerId with the callback function that will null the timerId after the delay. 
Here is the immediate version of the sandbox: 
https://codesandbox.io/s/es6-debounce-immediate-example-737vm?file=/src/index.js


## Combined all 
```
const debounce = (func, delay, immediate) => {
  let timerId;
  return (...args) => {
    const boundFunc = func.bind(this, ...args);
    clearTimeout(timerId);
    if (immediate && !timerId) {
      boundFunc();
    }
    const calleeFunc = immediate ? () => { timerId = null } : boundFunc;
    timerId = setTimeout(calleeFunc, delay);
  }
}
```

for the bonus, we can change this to throttle as well. Only differences is, timing of resetting the `timerId`. For throttle, we don't `clearTimeout`, we just `null` the `timerId` after the execution of the function.
```
const throttle = (func, delay, immediate) => {
  let timerId;
  return (...args) => {
    const boundFunc = func.bind(this, ...args);
    if (timerId) {
      return;
    }
    if (immediate && !timerId) {
      boundFunc();
    }
    timerId = setTimeout(() => {
      if(!immediate) {
        boundFunc(); 
      }
      timerId = null; // reset the timer so next call will be excuted
    }, delay);
  }
}
```
https://codesandbox.io/s/es6-throttle-example-2702s?file=/src/index.js