6
\$\begingroup\$

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."

\$\endgroup\$
1
  • \$\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
    Jan 22, 2019 at 20:23

1 Answer 1

6
\$\begingroup\$

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 be timeoutHandle, or in context just id or handle will do.
  • The is no need to assign null to the variable timeoutHandler. undefined variable are undefined and making it null 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 for clearTimeout, setTimeout, and console so why for addEventListener?
  • addEventListener('oninput'... is incorrect, there is no event called oninput. Should be addEventListener('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">

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.