1
\$\begingroup\$

I built one JavaScript debounce function. I need a JavaScript expert's opinion if this is the correct way to do it, and if not, then what the flaw is in this current function.

var debounce = function(inpFun, wait) {
    var timeout;
    return function () {
        if(!timeout) {
            inpFun.apply(this, arguments);
            timeout = setTimeout(function() {
                timeout = undefined;
            }, wait);
        }
        else {
            console.log("Debouncing");
        }
    }
};

var buttonClickFunction = debounce(function (event) {
    console.log("Button Clicked");
    console.log(event.target.id);
}, 2000);



document.querySelector("#button1").addEventListener("click", buttonClickFunction);

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Event rate limiter

It is important to use the correct terms when defining functions. If you are unsure it is always better to look up the literature to ensure you are using the term correctly.

Debouncing

You have not implemented a de-bounce. De-bounce delays action on an event to prevent it firing in rapid succession.

The term comes from ye old day hardware where the mechanical switch would have a tendency to bounce on the contacts, giving a series of on/off signals when only one was intended.

In Javascript we use de-bounce to prevent events that fire at high rates (eg mouse resize) from firing at a rate higher than is useful, (eg screen refresh rate.)

You could implement a de-bounce as follows

const debounce = {
    debounceTime : 33, // in ms
    timeoutHandle : undefined,
    ondebounced(event) {
        log("Resized");
    },
    event(event) {
        clearTimeout(debounce.timeoutHandle);
        debounce.timeoutHandle = setTimeout(debounce.ondebounced, debounce.debounceTime, event)
    }
}
addEventListener("resize",debounce.event);

It will wait 33ms after the last resize event before calling the de-bounced function.

Rate limiter

You have implemented a rate limiter, that forces an event to fire at a set maximum rate. The difference are minor,

  • De-bounce fires after the events (signal noise) while a rate limiter fire as soon as possible.
  • De-bounce delay is indeterminate, while rate limiting has a fixed determinate delay.

Your code

Your code is a little old school. You should keep up with the lates JS version ES8.

  • You should be using const when you intended variables not to change.
  • Use arrow functions.
  • Use the spread / rest tokens for passing unknown arguments. See example

You also are mixing a variable's use. The var timeout. This may not cause you trouble at the moment and seem like a good idea to reduce code size by doubling a variables meaning, (means timeoutHandle, and Block events) But will bite if in future you make changes and forget that timeout is more than just a handle.

Never mix a variables meaning without making it clear in the name

Example

The following is an example of how you could implement a rate limiter.

The functionality is encapsulated and access is only via getters and setters to prevent miss use. See comments for more. This is only an example, not a definitive how too.

    // Encapsulated rateLimit event utility
    const rateLimit = (()=>{
        const rate = 2000;  // the max rate in ms
        
        // the limiting functions, pass the event to call
        // returns an API for handling the events.
        return function (eventFunc){
            var handle;  // for unblock event
            var blocked = false;  // true if blocked
            var event;  // the event to call if not blocked
            // this function handles the incoming event
            const eventHandler = (...eventArgs) => {
                if(blocked){
                    log("Event blocked. Too fast.")
                }else{
                    API.block = true;
                    if (event) { event(...eventArgs) }
                }
            }
               
            // the API 
            // block bool (write only) set to true will block events for
            // rate but only if not already blocked. Set to false to clear any blocking
            // blocked returns (read only) bool for current blocking state
            // onlimitedevent  read/write function to call on event. If not a function then clears the event
            // event read only gets the event to attach to the listener
            const API = {
                set block(value) {
                    if (value && !blocked) { 
                        handle = setTimeout(()=>API.block = false, rate);
                        blocked = true;
                    } else if (!value && blocked) { 
                        clearTimeout(handle); // incase unblock is from outside
                        blocked = false 
                    
                    }
                },
                get blocked() { return blocked },
                set onlimitedevent(func) { event = typeof func === "function"  ? func : undefined },
                get onlimitedevent() { return event },
                get event() { return eventHandler },
        

            }
            API.onlimitedevent = eventFunc;
            return API;
        };
        
    })();

    const clicker = rateLimit(() => {log("clicked")});
    document.body.addEventListener("click", clicker.event);
    log("Ready, click me to test.");

    function log(...data) { out.appendChild(Object.assign(document.createElement("div"),{textContent : data.toString()})) }
<code id="out"></code>

\$\endgroup\$

Your Answer

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

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