The throttle() function
A little while ago, I blogged about downshifting your code over at the YUI Blog. In that entry, I described a pattern for throttling code such that it won’t be called as frequently. This technique proves to be invaluable when dealing with the resize event in Internet Explorer, which fires the event repeatedly as the browser is being resized (whereas other browsers wait until after the user has finished resizing the browser). Since then, I’ve been looking for a more compact, reusable solution to throttling function calls. Tonight, I finally figured out a simple function that can be dropped in anywhere:
function throttle(method, scope) {
clearTimeout(method._tId);
method._tId= setTimeout(function(){
method.call(scope);
}, 100);
}
The throttle() function accepts two arguments: the method to call and the scope in which to call it (which may be null). A timeout ID is assigned onto the function (yes, I’m slightly modifying your function, but it’s worth it). This timeout ID is used to manage timeouts for the function. Because of this, it’s necessary to avoid using anonymous functions. For example, this won’t work as expected:
window.onresize = function(){
throttle(function(){
doSomething();
}, window);
};
The problem here is that whenever the resize event fires, a new anonymous function is created so the timeout ID is lost. Instead, use a previously defined function:
window.onresize = function(){
throttle(doSomething, window);
};
Using throttle() in this way ensures that the code is appropriately throttled. It can be tweaked by changing the value of the timeout delay; if you need the function to execute more frequently, change 100 to a smaller number; if you need the function to execute less frequently, change 100 to a larger number.
I’ve ended up using this technique, and now this function, more frequently than I thought I would. I hope you find it as useful as I do.
Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.
Both comments and pings are currently closed.




6 Comments
How about this:
YAHOO.lang.augmentObject(Function.prototype, {
throttle: function(timeout) {
if (YAHOO.lang.isUndefined(timeout)) timeout = 100;
var __method = this;
return function() {
clearTimeout(__method._tId);
var self = this;
var args = arguments;
__method._tId = setTimeout(function() {
__method.call(self, args);
}, timeout);
}
}
});
It does the right thing with scope as well as arguments. You can do use it like:
function doSomething() { /***/ }
window.onresize(doSomething.throttle(1000));
or to use the default throttle of 100ms
window.onresize(doSomething.throttle());
daaku on November 30th, 2007 at 2:31 pm
Did you test this? It doesn’t work the way you think it does. Run a few tests with arguments and you’ll see (it does not work with arguments). Plus, you’ve augmented the function’s prototype, modifying objects you don’t own is a big no-no.
Nicholas C. Zakas on November 30th, 2007 at 3:09 pm
It seems if throttle is called too fast (consistently faster than the timeout of 100ms) the call would never get through. The clearInterval at the beginning of the function would reset it so the only call to get through would be the last call.
The other concern I would have is the delay between a call to throttle and the call to the function even on the first call. At 100ms it probably isn’t a big issue but if the timeout were set to 1000ms or something it would probably be noticeable.
My first thought was to use the timeout to block for a certain period of time after a successful call instead of what the function is doing. But then I realized I could lose the last call which, for events like resize, is likely the most important one. So thinking outloud, I think I’d probably use two timeouts. One to block after a successful call and one to make sure the last call was successfully.
David S. on December 3rd, 2007 at 1:02 pm
I just read your comment in the original YUIBlog post and see now that stopping function calls until the last one is what you intend. I thought it was supposed to let calls through but at a slower pace. So nevermind my comment.
David S. on December 3rd, 2007 at 3:13 pm
I wrote a <a href="http://dedchain.dustindiaz.com/api/docs/#doc-throttle">throttle method</a> some time ago that I built into DED|Chain (a forgotten library now) which basically allows you to set the timer in seconds, and loops over a supplied stack. Alternatively you can pass in "loop: true" to the config object, and it will iterate continuously until you tell it to stop.
Dustin Diaz on December 5th, 2007 at 3:12 am
Nicholas, will you fix my comment. I can’t keep up with what blogs accept what kind of markup.
Dustin Diaz on December 5th, 2007 at 3:13 am
Comments are automatically closed after 14 days.