Function Debouncing with Underscore.js

By  on  

The ability to listen and react to user interactions with JavaScript is fundamental and incredibly useful. Some interactions happen frequently and some rarely. Some listener functions are light of action, others can be quite taxing on the browser. Take window's resize event for example: the event fires at each step in the resize, so if you have a taxing event listener, your user's browser will get bogged down quickly.

Obviously we can't allow the user's browser to get bogged down, but we can't simply remove listener function either. What we can do, however, is use debouncing to temper the amount of time the method runs. Instead of the listener function firing on each iteration of the resize event, we can ensure it fires only every n milliseconds during the resize, allowing our functionality to fire but at a rate so as to not ruin the user's experience. A great utility called Underscore.js provides an easy to use method for easily creating debouncing event listener functions.

The JavaScript

Creating a debouncing event listener is as easy as:

// Create the listener function
var updateLayout = _.debounce(function(e) {

	// Does all the layout updating here
	
}, 500); // Maximum run of once per 500 milliseconds

// Add the event listener
window.addEventListener("resize", updateLayout, false);

..because the Underscore.js code underneath the hood manages the interval checks and original listener function calling:

// 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.
_.debounce = function(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);
	};
};

Not the most complex piece of code but nice that you don't have to write it yourself. The debounce method doesn't rely on any other Underscore.js methods, so you can add this method to a framework like jQuery or MooTools quite easily:

// MooTools
Function.implement({
	debounce: function(wait, immediate) {
		var timeout, 
		    func = this;
		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);
		};
	}
});

// Use it!
window.addEvent("resize", myFn.debounce(500));

As mentioned above, window resize events are the most obvious place to use debouncing, but you could also use them for key events that trigger an autocompleter. I love tiny pieces of code like this that can enhance a site's efficiency so quickly! I also recommend you take a look at Underscore.js and the numerous utility functions it provides -- enrich your existing framework or use it as is!

Recent Features

  • By
    How I Stopped WordPress Comment Spam

    I love almost every part of being a tech blogger:  learning, preaching, bantering, researching.  The one part about blogging that I absolutely loathe:  dealing with SPAM comments.  For the past two years, my blog has registered 8,000+ SPAM comments per day.  PER DAY.  Bloating my database...

  • By
    Designing for Simplicity

    Before we get started, it's worth me spending a brief moment introducing myself to you. My name is Mark (or @integralist if Twitter happens to be your communication tool of choice) and I currently work for BBC News in London England as a principal engineer/tech...

Incredible Demos

  • By
    Pure CSS Slide Up and Slide Down

    If I can avoid using JavaScript for element animations, I'm incredibly happy and driven to do so.  They're more efficient, don't require a JavaScript framework to manage steps, and they're more elegant.  One effect that is difficult to nail down with pure CSS is sliding up...

  • By
    MooTools Zebra Tables Plugin

    Tabular data can oftentimes be boring, but it doesn't need to look that way! With a small MooTools class, I can make tabular data extremely easy to read by implementing "zebra" tables -- tables with alternating row background colors. The CSS The above CSS is extremely basic.

Discussion

  1. Aicke Schulz

    I want to mention, that there are already two similar solutions for MooTools, realized as “pseudo events”:

    http://mootools.net/docs/more/Class/Events.Pseudos#Pseudos:throttle
    http://mootools.net/docs/more/Class/Events.Pseudos#Pseudos:pause

  2. Rolf

    Yes yes, exactly why I ripped that method out of _.js code and put it inside my Moo code for window resize events :) – good tip to share!

  3. piotr

    In case you are already using Mootools in the project, consider Events.Pseudos :once, :throttle and :pause (http://mootools.net/docs/more/Class/Events.Pseudos)

    window.addEvent('resize:throttle(400)', function(){
        // Will only fire once every 400 ms
    });
  4. Alex

    You always post things just in time. THANKS!

  5. good one! i think it’s the same with Ben Alman’s debounce http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/ which is a jQuery plugin

  6. This is really useful. very snap and play which I like. I’m interested to see if this will work on a mobile device and what listeners other listeners I could use. Thanks for the post.

  7. Thanks, really interesting.

  8. Albert

    Great, just what I needed. Can’t use MooTools event.pseudos because I’m bounded to version 1.2.5

  9. Alfonso Perez

    This is the nth-time I benefit from one of your posts, so I have decided to take some seconds to say: thanks!,

    btw, as my humble way of saying thanks, I have paused for a moment adblock and show some genuine interest for your sidebar ad ;-).

  10. Volkan

    Why they used the code like that?

    func.apply(context, args);
    

    I can change it with func() and it executes as is.

  11. Guy

    Hi,
    I know this is an old post but it’s still coming up as the second hit for the google search ‘underscore debounce’ so I thought I’d chip in.

    The function you’re describing in the 2nd paragraph – “…we can ensure it fires only every n milliseconds during the resize..” seems to actually be _throttle.
    _debounce only calls the given function once, an amount of time only after it has stopped being continually fired.

    Cheers!

  12. Dick

    Damn confusing code, your documentation skills and variable naming needs improving. There’s simpler code to do this on the net too.

    • NotHim

      @Dick As stated in the article itself, this code is part of the Underscore.js library, not his code. If you have issues with the documentation and variable naming, bring it up to the Underscore.js developers.

  13. S

    So immediate only applies to the first click? I was assuming that it made all clicks immediate, so i was really confused why the timeout variable was being compared in callnow.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!