When interactive applications use events to handle user actions, app performance can suffer. Debouncing resolves that problem by executing the event handler only once. Here's how it works.
Introduction to Event Debouncing
Interactive applications use events to handle user actions such as typing, touching, or moving the mouse. Some of these events fire often and don’t have to be handled individually. The following scenarios are typical:
- Updating a filter as the user types in a search term.
- Updating information about selected items no a list as the user modifies the selection.
- Updating the application’s layout as the user resizes the browser window.
- Updating views when batches of changes are applied to models/controllers.
In all of these cases, the events happen in bursts (characters being typed, mouse or touch moves, or property change notifications). In all of them, it would be better to perform the update at the end of the last event rather than after each individual event.
Solution
This is a common challenge, and there is a standard way to solve it. It's called “debouncing”, which means executing the event handler only a certain amount of time after the event stops firing.
Many applications offer built-in debouncing mechanisms. For example:
- The Underscore.js library has a debounce method that converts any given method into a “debounced” version which postpones execution until after an interval has elapsed since the last time it was invoked. For details, see http://underscorejs.org/#debounce.
- AngularJS’s ngModelOptions directive has a debounce property that postpones model updates until an interval has elapsed since the last change in an input element. For details, see https://docs.angularjs.org/api/ng/directive/ngModelOptions.
The debounce method used by Underscore.js is simple and very flexible. You can use it to create debounced versions of any functions, including of course event handlers. Here is a typical implementation:
function debounce(fn, delay) {
var timer = null;
return function() {
var self = this,
args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(self, args);
}, delay);
};
}
The function takes as parameters the function to be debounced and the interval to use before executing it. It returns the debounced function, which when called invokes the original function from a setTimeout. Calling the debounced several times with intervals shorter than the delay will cause it to clear any previous time outs and to execute only once at the end of the burst.
Here’s a typical use for the debounce function:
// get a reference to the input that specifies the filter string
var filter = document.getElementById('theFilter');
if (false) {
// handle each input event individually
filter.addEventListener('input', updateFilter);
} else {
// handle input events when the user stops typing for 500ms
var handler = debounce(updateFilter, 500);
filter.addEventListener('input', handler);
}
// potentially expensive event handler
function updateFilter(e) {
applyFilter(filter.value);
}
The debounce function makes it trivial to create debounced versions of any event handlers (or any functions at all). You don’t have to make any changes whatsoever to the original function.
You can use debounce with DOM events as well as with Wijmo events. For example:
// create a FlexGrid control
var flex = new wijmo.grid.FlexGrid('#theGrid');
// add a debounced handler to the grid’s selectionChanging event
var handler = debounce(selectionChanging, 250);
flex.selectionChanging.addHandler(handler, this);
// regular Wijmo event handler
function selectionChanging(s, e) {
var sel = e.range;
console.log('selChange: (' + sel.row + ', ' + sel.col + ')');
}
This fiddle shows how debounce works with DOM and Wijmo events: http://jsfiddle.net/Wijmo5/v04d97cq/
When you run the fiddle, notice how selection becomes sluggish on the top grid. That is because the selectionChanging event handler gets called every time the selection changes, and it has a delay that simulates a lengthy operation such as a server request based on the new selection.
The bottom grid uses a debounced version of the same event handler. Selection in this case is fast, because the handler gets called only 500ms after the user stops changing the selection.
Conclusion
Debouncing is a great technique for improving application performance. Calling event handlers less can not only make applications faster, but also reduce flicker and improve the user experience.
Debouncing is typically applied to DOM event handlers, but it can also be applied to Wijmo event handlers and to any function that is called frequently and causes application updates.
Best of all, the technique requires only minor changes to existing code.
You can read more about debouncing and throttling here: