Delaying execution of JavaScript with debounce

AJAX is really cool. Being able to give users feedback almost instantaneously is a great for enhancing usability for your site. But there’s a downside.

Let’s say we want to grab some search results as a user types. We can do something like this

var callSearchFunction = function(e) {
    getNewSearchResults();
}

document.getElementById('searchbar')
        .addEventListener("keyup", callSearchFunction);

This would probably work fine as long as only a few users at a time are using your live search. But how could we do the same thing and scale it a little better without melting our $10 Linode VPS?

Debouncing

If you’re doing any kind of heavy AJAX with JavaScript, such as real time search results, you need some kind of debounce — a function that delays execution of code until a set time has passed without any other input.

I’m borrowing from underscore.js’ _.debounce() method to start off with, because people who are likely much smarter than me wrote and tested it

// Returns a function, that, as long as it continues to be invoked,
// will not be triggered. The function will be called after it stops
// being called for N milliseconds. If immediate is passed, trigger
// the function on the leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
  var timeout, args, context, timestamp, result;

  var later = function() {
    var last = Date().getTime() - timestamp;

    if (last < wait && last >= 0) {
      timeout = setTimeout(later, wait - last);
    } else {
      timeout = null;
      if (!immediate) {
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      }
    }
  };

  return function() {
    context = this;
    args = arguments;
    timestamp = Date().getTime();
    var callNow = immediate && !timeout;
    if (!timeout) timeout = setTimeout(later, wait);
    if (callNow) {
      result = func.apply(context, args);
      context = args = null;
    }

    return result;
  };
};

This function takes a callback, a time (in milliseconds) and an “immediate” flag in case we want to bypass the debounce all together in certain scenarios. So how do we integrate this into our search?

The only thing we need to do here is encase our search callback in the debounce function, and tell it how long we want to wait before shooting off our query to the server

var callSearchFunction = debounce(function() {
    getNewSearchResults();
}, 500);

Now, our search will only fire off once the user has not typed for half a second (500 milliseconds).

You could also add in other checks to make your life a little easier, like checking to make sure a search has at least three characters before searching.

Full Code

// Returns a function, that, as long as it continues to be invoked,
// will not be triggered. The function will be called after it stops
// being called for N milliseconds. If immediate is passed, trigger
// the function on the leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
    var timeout;
    return function() {
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

var callSearchFunction = debounce(function() {
    alert("I'm ready!");
}, 750);

var callSearchFunction = function(e) {
    getNewSearchResults();
}

document.getElementById('searchbar')
         .addEventListener("keyup", callSearchFunction);

 

Leave a Reply

Your email address will not be published.