skip to Main Content

I am currently learning debounce in Javascript and I came across two ways of writing debounce functions that works the same. One is a lot simpler like regular function, the other one is the complicated one that everyone seems to use.

Version 1:

@index.html

<input type="text" oninput="betterFunction()">

@script.js

function getData() {
  console.log('Data Fetched')
}

function debounce(callback, delay) {
  let timer
  return function() {
    clearTimeout(timer)
    timer = setTimeout(() => {
      callback();
    }, delay)
  }
}

const betterFunction = debounce(getData, 1000)

Version 2:

@index.html

<input type="text" oninput="debounce()">

@script.js

let timer

function debounce() {
  clearTimeout(timer)
  timer = setTimeout(() => {
    console.log('Data Fetched');
  }, 1000)
}

What is the difference between these two ways of debouncing if they both gives the same result?
PS: I am surprised that I have never seen anyone use the ‘version 2’, ofcourse something must me wrong. Could anybody explain the differences please?

2

Answers


  1. Version 1 is better, because it:

    • encapsulates the timer (timeout ID) within the scope of—the lifetime of—the function call
    • eliminates nasty global variables which could accidentally be modified outside or within another function
    • most of all, it is reusable

    An even-better debounce

    Josh W Comeau has an informative article covering debounce.

    Here is his (modified) minimal version:

    const debounce = (callback, wait) => {
      let timeoutId = null;
      return (...args) => {
        window.clearTimeout(timeoutId);
        timeoutId = window.setTimeout(() => {
          callback(...args);
        }, wait);
      };
    }
    

    Note: I replaced the callback.apply(null, args) with the more succinct callback(...args)

    Usage

    const handleMouseMove = debounce((mouseEvent) => {
      // Do stuff with the event!
    }, 250);
    
    document.addEventListener('mousemove', handleMouseMove);    // Add listener
    document.removeEventListener('mousemove', handleMouseMove); // Remove listener
    

    Snippet

    In the snippet below, points are drawn every time the user stops moving the mouse after 250ms. Each point is auto-removed after 2 seconds.

    const debounce = (callback, wait) => {
      let timeoutId = null;
      return (...args) => {
        window.clearTimeout(timeoutId);
        timeoutId = window.setTimeout(() => {
          callback(...args);
        }, wait);
      };
    }
    
    const createPoint = (x, y, color) => {
      const point = Object.assign(document.createElement('div'), { className: 'point' });
      Object.assign(point.style, { top: `${y - 2}px`, left: `${x - 2}px`, background: color });
      document.body.append(point);
      return point;
    };
    
    // Log mouse coordinates when user stops moving mouse after 250ms
    const handleMouseMove = debounce(({ clientX: x, clientY: y }) => {
      console.log(`Creating MOVE point at: (${x}, ${y})`);
      const point = createPoint(x, y, 'white');
      // Auto-remove after 1 second
      setTimeout(() => {
        console.log(`Removing MOVE point at: (${x}, ${y})`);
        point.remove();
      }, 2000);
    }, 250);
    
    // Log mouse coordinates when user stops clicking 250ms
    const handleClick = debounce(({ clientX: x, clientY: y }) => {
      console.log(`Creating CLICK point at: (${x}, ${y})`);
      const point = createPoint(x, y, 'red');
      // Auto-remove after 1 second
      setTimeout(() => {
        console.log(`Removing CLICK point at: (${x}, ${y})`);
        point.remove();
      }, 2000);
    }, 250);
    
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('click', handleClick);
    .as-console-wrapper { max-height: 5em !important; }
    html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
    body { background: #222; position: relative; }
    .point { position: absolute; width: 4px; height: 4px; border-radius: 50%; background: #FFF; }
    Login or Signup to reply.
  2. Debounce function is a javascript programming pattern for delaying execution of a function, your Version 1 is the commonly accepted pattern for implementing a debounce feature because its better than Version 2 for the reasons listed by MR. Polywhirl.

    The pattern is actually making use of a javascript feature called a closure, which allows inner function (the function being returned by debounce) to access their outer scope (specifically in this case, the declared variable timer).

    You can read about closures in detail here to gain further understanding why version 1 is the preferred version, but this explanation/example of closures might be easier to grasp at first.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search