function debounce(fn, wait) {
let timer;
function debounced(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), wait);
}
debounced.cancel = () => clearTimeout(timer);
return debounced;
}
The debounced function keeps a timer id in a closure. On every call, it clears the waiting timer and starts a new one. So fn runs only after wait ms pass with no new call.
Two details make an answer stand out:
- Use a regular
functionfor the wrapper and callfn.apply(this, args). This passes on the originalthisand the latest arguments. It matters when the debounced function is used as a method. - The arrow function inside
setTimeouttakes thatthisfrom the wrapper, because arrows have nothisof their own.
Common extensions:
- a
cancel()method that clears the timer, useful when a component unmounts - a
leadingoption that runsfnon the first call - a
maxWaitoption that forces a run during a never-ending stream of calls
- Store the timer id in a closure.
- On every call, run
clearTimeout, thensetTimeout. - Pass on
thisand the latest arguments withapply. - Common extensions are
cancel, a leading edge andmaxWait.
Read more