Please, review the code:
const callback = (...args) => {
console.count('callback throttled with arguments:', args);
}
const debounce = (callback, delay) => {
let timeoutHandler = null
return (...args) => {
if (timeoutHandler) {
clearTimeout(timeoutHandler)
}
timeoutHandler = setTimeout(() => {
callback(...args)
timeoutHandler = null
}, delay)
}
}
window.addEventListener('oninput', debounce(callback, 100))
P.S. As @Anshul explained: Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called. As in "execute this function only if 100 milliseconds have passed without it being called."
-
\$\begingroup\$ Welcome to Code Review! I'm always somewhat surprised when people don't understand debounce, but it doesn't appear to be a common term outside of electromechanical engineering. For future reference, you can refer to this page, with a note that debouncing can also be done for any type of (UI) button. \$\endgroup\$Mast– Mast ♦2019年01月22日 20:23:46 +00:00Commented Jan 22, 2019 at 20:23
1 Answer 1
DELAY V DEBOUNCE
What you have implemented is a delay not a debounce. You delay the firing of the event until after a set time. Events during this time further delay the final event.
Design
Debouncing should respond to the first event immediately and then hold any response during the debouncing period after the listener has been called.
It would be better to clearly identify the event argument passed to the listener.
You do not provide a way of passing additional arguments to the event. By adding them to the outer function, you get better control. (see example)
General points
- Inconsistent use of semicolon. Either use it (recommended) or not. Don't mix styles.
var
is function scoped,let
os block scope. It is better to use var when a variable is declared in function scope.timeoutHandler
is a poor name. The timeout function returns and Id (AKA a handle). The name you used implies that the variable is a function. A better name could betimeoutHandle
, or in context justid
orhandle
will do.- The is no need to assign null to the variable
timeoutHandler
. undefined variable areundefined
and making itnull
is just noise and semantically incorrect - The handle returned by setTimeout (and setInterval) are unique to the context and the call. It is never reused.
clearTimeout
ignores invalid handles so there is no need check if the handle is valid, nor change its value inside the timeout function. - You can reduce the call stack depth by using the trailing arguments of
setTimeout
to pass the arguments. window
is the default object. Inconsistent use is poor style. You do not use it forclearTimeout
,setTimeout
, andconsole
so why foraddEventListener
?addEventListener('oninput'...
is incorrect, there is no event calledoninput
. Should beaddEventListener('input'...
Examples
Covering the general points and comparing debounce, delay, and immediate event responses. The delay is also used to clear the displays 2sec after the last event.
It also shows when to use window. In the example emoticons can not be used as direct references to elements, thus I use window bracket notation to access the names.
const DELAY_MS = 250;
const CLEAR_DELAY_MS = 2000;
const CLEAR_SPEED_MS = 17;
const DISPLAYS = ["💥","💤","⚡"];
const logger = (event, ...args) => log(args[0]);
const delay = (callback, delay, ...args) => {
var handle;
return event => {
clearTimeout(handle);
handle = setTimeout(callback, delay, event, ...args);
}
}
const debounce = (callback, delay, ...args) => {
const timerClear = () => clear = true;
var clear = true;
return event => {
if (clear) {
clear = false;
setTimeout(timerClear, delay);
callback(event, ...args);
}
}
}
addEventListener('input', debounce(logger, DELAY_MS, DISPLAYS[0]));
addEventListener('input', delay(logger, DELAY_MS, DISPLAYS[1]));
addEventListener('input', event => logger(event, DISPLAYS[2]));
addEventListener('input', delay(clear, CLEAR_DELAY_MS));
gun.focus();
/* Helper functions unrelated to question */
function clear() {
var allClear = "";
DISPLAYS.forEach(name => {
var text = window["log" + name].textContent;
allClear += window["log" + name].textContent = text.substring(1);
})
if (allClear !== "") {
setTimeout(clear, CLEAR_SPEED_MS);
} else {
gun.value = "";
}
}
function log(which) { window["log" + which].textContent += which }
b { font-size : large }
<code>
<b>Rumble!! Debounce V Delay V Immediate. With a 2 second delayed clear.</b></br>
Debounced 250ms: <span id="log💥"></span></br>
Delayed.. 250ms: <span id="log💤"></span></br>
Immediate.. 0ms: <span id="log⚡"></span></br>
</code>
</br>
<input type="text" placeholder="Rapid fire key strokes" size="50" id="gun">