february 5, 2024

{ delay a javascript function with debouncing }


tags: javascript, learning

I came across the idea of "debouncing" last week. Although I am using debounce from the lodash library in my work, I wanted to give writing out my own version of debounce a try. Here's a quick look at my results.

What is Debouncing?

Debouncing is the act of preventing a repeated action for a certain period of time. I'd never heard the term before, but the effect is extremely prevalent. Since I've been benefitting from this idea for a lifetime, it is nice to have a name for it.

In software, debouncing is used in situations like:

  • Instant search: With live searching, debounce lets an app delay search functionality so that it it doesn't have to perform a search after every character entered. Instead, it only performs the search every few seconds.
  • Form submissions: Debounce can momentarily disable a button. This is useful if you have, say, a button that will process payment. If someone presses the button twice before the request goes through, they might get their card charged twice. Debounce gives the app time to make the request and redirect.
  • Continuous scroll: When a website has continuous scroll, we don't want to load new content for every pixel the user scrolls. Debounce postpones loading content so the app can load a larger portion of content at once rather than pixels at a time.

In all, debounce offers performance benefits. It reduces how frequently time consuming actions are done, improving user experience.

The term debounce comes from hardware. Electrical signals travel very quickly, but physical movement takes longer. When you press a button on a remote, for instance, several signals can be sent in the time it takes you to release the button. Debouncing prevents duplicate signals from going through in that time.

Writing Debounce in JavaScript

The first challenge for me was wrapping my mind around what the code needed to do. I initially wrote a very good function that did entirely the wrong thing, but here's what I came to understand.

Debounce is a wrapper. It will take the function we are going to postpone and a wait time, in milliseconds.

It can then behave in a couple different ways:

  • The wrapped function runs only after the timer has expired.
  • The wrapped function runs immediately and cannot run again until the timer has expired and the action is triggered again.
  • The wrapped function runs immediately and will run again once the timer expires.

When a user performs the triggering action, the wait time starts. Every additional action resets that timer. If a user were to continuously perform the action, the action would be postponed indefinitely.

How to Use Closure to Create Debounce

I decided to start with the first case only: the wrapped function runs only after the timer has expired.

As a wrapper, debounce relies on closure. This is what lets the timer persist between function calls. I've created a timer variable in the scope of debounce. I'll update this variable each time debounce runs, assigning it a new timer.

debounce returns an inner function which uses setTimeout to call the function we are wrapping after the specified time. It also clears the existing timer so that we don't have multiple timer instances.

function debounce(fn, wait) {

    let timer;  

    function setTimerForFnRun() {
        clearTimeout(timer);
        timer = setTimeout(fn, wait);
    }

    return setTimerForFnRun;
}

Using Debounce with Lodash

Now that I know how text suggestion and instant search can be more performantly accomplished, I've got a project that I can't wait to implement this on. That said, I will most likely be using debounce included with lodash.

lodash provides several optional parameters to accomplish the different cases of debounce:

  • leading: if true, the wrapped function runs at the start of the timer
  • trailing: if true, the wrapped function runs at the end of the timer
  • maxWait: a time, in milliseconds, at which the wrapped function will run if it hasn't yet (to prevent it from being delayed indefinitely)

And that's debounce. I'm trying to think of other places to use this besides the ones I mentioned. Let me know if you have any ideas.