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 to Create a Twitter Card

    One of my favorite social APIs was the Open Graph API adopted by Facebook.  Adding just a few META tags to each page allowed links to my article to be styled and presented the way I wanted them to, giving me a bit of control...

  • By
    An Interview with Eric Meyer

    Your early CSS books were instrumental in pushing my love for front end technologies. What was it about CSS that you fell in love with and drove you to write about it? At first blush, it was the simplicity of it as compared to the table-and-spacer...

Incredible Demos

  • By
    spellcheck Attribute

    Many useful attributes have been provided to web developers recently:  download, placeholder, autofocus, and more.  One helpful older attribute is the spellcheck attribute which allows developers to  control an elements ability to be spell checked or subject to grammar checks.  Simple enough, right?

  • By
    HTML5’s placeholder Attribute

    HTML5 has introduced many features to the browser;  some HTML-based, some in the form of JavaScript APIs, but all of them useful.  One of my favorites if the introduction of the placeholder attribute to INPUT elements.  The placeholder attribute shows text in a field until the...

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!