DEV Community

loading...

Refactor davidwalsh's debounce function using ES6 arrow and more

monaye profile image Monaye Win Updated on ・1 min read

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, 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);
  }
}
Enter fullscreen mode Exit fullscreen mode

with this code, it's easier to read the logic behind the code. you can see easily that we are restting timerId to null on delay when immediate.

if you don't need immediate call this can be simpler of course.

const debounce = (func, delay) => {
  let timerId; 
  return (...args) => {
    const boundFunc = func.bind(this, ...args); 
    clearTimeout(timerId);
    timerId = setTimeout(boundFunc, delay);
  }
}
Enter fullscreen mode Exit fullscreen mode

for the bonus, we can change this to throttle as well

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; 
    }, delay);
  }
}
Enter fullscreen mode Exit fullscreen mode

Discussion (4)

pic
Editor guide
Collapse
martinhinze profile image
martin-hinze

Hey Monaye,
your simplified version of the function without the immediate argument is very helpful, thank you, I learned a lot and begin understanding debouncing (slowy).
I like how you refer to David Walsh's original post as some classical legacy.
However, it is not from 2004, but from 2014. :-)
Best wishes,
Martin

Collapse
monaye profile image
Monaye Win Author

Oops.. Thank you :wink

Collapse
kelvinzhao profile image
Kelvin Zhao

How would you refactor the 'once' function?

function once( fn, context ) {
    let result
    return function() {
        if( fn ) {
            result = fn.apply( context || this, arguments )
            fn = null
        }
        return result
    }
}
Collapse
thexsdev profile image
thexs-dev

I will be using your improved ES6 debounce version from now on ... Thanks!

On a side note
There is a less popular but quite useful function async-debounce from Julian Gruber that

  • not just run after no calls to it have happened for x milliseconds
  • but also skips calls while the function is currently running to avoid concurrency
  • like when the debounced function calls an Api that take some time to respond (e.g. querying or heavy filtering responding to user's keystrokes)

Is there a ES6 approach to that or another way of achieving that same result?