Writing my own throttle
I wrote recently about finding a modular throttle function. I wasn’t happy using that one, primarily because it is a rewrite of the Underscore.js throttle. The Underscore project does great work, but I really wanted to understand this ‘low-level’ function. For me, doing is understanding. Furthermore, Underscore’s throttle relies on Date.now()
, which gives it a kind of ‘memory’. Its behavior isn’t consistent between the first time you fire it, and firing it after waiting a while. A throttle’s throttling should always be relative.
So I have been researching and sampling various throttle functions since then. I appreciated some from a readability angle, but they had issues. They wouldn’t fire on the trailing edge. Or they modified the user’s function (and weren’t even a throttle, Zakas’ throttle is actually a debounce).
So I have been going back and forth on throttle ideas. Writing them out on my whiteboard, on notebook paper before I fall asleep, testing them in zero gravity, et cetera. I have meditated on all things throttle. Why? Because a throttle is rudimentary to so many libraries, I could not continue writing JavaScript libs until I had a throttle I could call my own. Until I had a throttle I could easily write and understand from memory. The other day I realized that the issue with Sampson’s throttle()
could be solved by combining it with Zakas’ debounce()
. So after weeks of trying things that didn’t work at all, I came up with this:
(function() {
'use strict';
var modThrottle = function(func, delay) {
var waiting = false;
return function() {
if (!waiting) {
waiting = true;
clearTimeout(func.MODTHROTTLE___TIMEOUT___ID);
func.MODTHROTTLE___TIMEOUT___ID = setTimeout(function() {
func.call();
waiting = false;
}, delay);
}
};
};
window.modThrottle = modThrottle;
}());
What I don’t like is that it modify’s the user’s function, then I realized that could be worked around by cloning the function with bind()
.
(function() {
'use strict';
var modThrottle = function(func, delay) {
var waiting = false,
funcClone = func.bind();
return function() {
if (!waiting) {
waiting = true;
clearTimeout(funcClone.MODTHROTTLE___TIMEOUT___ID);
funcClone.MODTHROTTLE___TIMEOUT___ID = setTimeout(function() {
funcClone.call();
waiting = false;
}, delay);
}
};
};
window.modThrottle = modThrottle;
}());
Much better! Now we aren’t modifying the function we bring in. That was just messy. But wait, if you’re thinking, why not just use a var
, you’re right on the money!
(function() {
'use strict';
var modThrottle = function(func, delay) {
var waiting = false,
funcTimeoutId;
return function() {
if (!waiting) {
waiting = true;
clearTimeout(funcTimeoutId);
funcTimeoutId = setTimeout(function() {
func.call();
waiting = false;
}, delay);
}
};
};
window.modThrottle = modThrottle;
}());
Now the code is simple, lightweight, readable, and isn’t violating any best practices. This throttle()
could be used internally like so:
function modThrottle(func, delay) {
'use strict';
var waiting = false,
funcTimeoutId;
return function() {
if (!waiting) {
waiting = true;
clearTimeout(funcTimeoutId);
funcTimeoutId = setTimeout(function() {
func.call();
waiting = false;
}, delay);
}
};
}
You can also easily reduce this to a debounce()
:
function modDebounce(func) {
'use strict';
var funcTimeoutId;
return function() {
clearTimeout(funcTimeoutId);
funcTimeoutId = setTimeout(function() {
func.call();
}, 210);
};
}
Now this is pretty much complete, but I think code should have a bit of attitude. So I decided to have my throttle tell a story. Enter Odis. Odis is the Latin god of modular limitation, let’s take a short look at the reusable version of Odis you can drop right inside of your library today:
You’ll notice that we have defaults for both delay
arguments now (the last argument for a function should be optional), and we are only creating one global object (or none if you use this internally).