I am looking for a simple throttle in JS. I know libraries like lodash and underscore have it, but only for one function it will be overkill to include any of those libraries.
I was also checking if jquery has a similar function - could not find.
I have found one working throttle, and here is the code:
function throttle(fn, threshhold, scope) {
threshhold || (threshhold = 250);
var last,
deferTimer;
return function () {
var context = scope || this;
var now = +new Date,
args = arguments;
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
}
The problem with this is: it fires the function once more after the throttle time is complete. So let's assume I made a throttle that fires every 10 seconds on keypress - if I do keypress 2 times, it will still fire the second keypress when 10 seconds are completed. I do not want this behavior.
callback: takes the function that should be called
limit: number of times that function should be called within the time limit
time: time span to reset the limit count
functionality and usage: Suppose you have an API that allows user to call it 10 times in 1 minute
function throttling(callback, limit, time) {
/// monitor the count
var calledCount = 0;
/// refresh the `calledCount` varialbe after the `time` has been passed
setInterval(function(){ calledCount = 0 }, time);
/// creating a closure that will be called
return function(){
/// checking the limit (if limit is exceeded then do not call the passed function
if (limit > calledCount) {
/// increase the count
calledCount++;
callback(); /// call the function
}
else console.log('not calling because the limit has exceeded');
};
}
////////////////////////////////////////////////////////////
// how to use
/// creating a function to pass in the throttling function
function cb(){
console.log("called");
}
/// calling the closure function in every 100 milliseconds
setInterval(throttling(cb, 3, 1000), 100);
Here is a throttle function that is identical to vsync's solution, with the addition of passing the arguments to the throttled function.
function throttle (callback, limit) {
var wait = false;
return function () {
if (!wait) {
callback.apply(null, arguments);
wait = true;
setTimeout(function () {
wait = false;
}, limit);
}
}
}
Can be used like so:
window.addEventListener('resize', throttle(function(e){console.log(e)}, 100));
Use without the addEventListener context:
throttle(function(arg1, arg2){console.log(arg1, arg2);}, 100)('red', 'blue');
// red blue
Adding to the discussion here (and for more recent visitors), if the reason for not using the almost de facto throttle
from lodash
is to have a smaller sized package or bundle, then it's possible to include only throttle
in your bundle instead of the entire lodash
library. For example in ES6, it would be something like:
import throttle from 'lodash/throttle';
Also, there is a throttle
only package from lodash
called lodash.throttle
which can be used with a simple import
in ES6 or require
in ES5.
Here's how I implemented throttle function in ES6 in 9LOC, hope it helps
function throttle(func, delay) {
let timeout = null
return function(...args) {
if (!timeout) {
timeout = setTimeout(() => {
func.call(this, ...args)
timeout = null
}, delay)
}
}
}
Click on this link to see how it works.
I've just needed a throttle/debounce function for window resize event, and being curious, I also wanted to know what these are and how they work.
I've read multiple blog posts and QAs on SO, but they all seem to overcomplicate this, suggest libraries, or just provide descriptions and not simple plain JS implementations.
I won't provide a description since it's plentiful. So here's my implementation:
function throttle(callback, delay) {
var timeoutHandler = null;
return function () {
if (timeoutHandler == null) {
timeoutHandler = setTimeout(function () {
callback();
clearInterval(timeoutHandler);
timeoutHandler = null;
}, delay);
}
}
}
function debounce(callback, delay) {
var timeoutHandler = null;
return function () {
clearTimeout(timeoutHandler);
timeoutHandler = setTimeout(function () {
callback();
}, delay);
}
}
These might need tweaks (e.g., initially the callback isn't called immediately).
See the difference in action (try resizing the window):
function throttle(callback, delay) {
var timeoutHandler = null;
return function () {
if (timeoutHandler == null) {
timeoutHandler = setTimeout(function () {
callback();
clearInterval(timeoutHandler);
timeoutHandler = null;
}, delay);
}
}
}
function debounce(callback, delay) {
var timeoutHandler = null;
return function () {
clearTimeout(timeoutHandler);
timeoutHandler = setTimeout(function () {
callback();
}, delay);
}
}
var cellDefault = document.querySelector("#cellDefault div");
var cellThrottle = document.querySelector("#cellThrottle div");
var cellDebounce = document.querySelector("#cellDebounce div");
window.addEventListener("resize", function () {
var span = document.createElement("span");
span.innerText = window.innerWidth;
cellDefault.appendChild(span);
cellDefault.scrollTop = cellDefault.scrollHeight;
});
window.addEventListener("resize", throttle(function () {
var span = document.createElement("span");
span.innerText = window.innerWidth;
cellThrottle.appendChild(span);
cellThrottle.scrollTop = cellThrottle.scrollHeight;
}, 500));
window.addEventListener("resize", debounce(function () {
var span = document.createElement("span");
span.innerText = window.innerWidth;
cellDebounce.appendChild(span);
cellDebounce.scrollTop = cellDebounce.scrollHeight;
}, 500));
table {
border-collapse: collapse;
margin: 10px;
}
table td {
border: 1px solid silver;
padding: 5px;
}
table tr:last-child td div {
width: 60px;
height: 200px;
overflow: auto;
}
table tr:last-child td span {
display: block;
}
<table>
<tr>
<td>default</td>
<td>throttle</td>
<td>debounce</td>
</tr>
<tr>
<td id="cellDefault">
<div></div>
</td>
<td id="cellThrottle">
<div></div>
</td>
<td id="cellDebounce">
<div></div>
</td>
</tr>
</table>
I made a npm package with some throttling functions:
npm install function-throttler
Returns a version of your function that can be called at most every W milliseconds, where W is wait. Calls to your func that happen more often than W get queued up to be called every W ms
Returns a version of your function that can be called at most every W milliseconds, where W is wait. for calls that happen more often than W the last call will be the one called (last takes precedence)
limits your function to be called at most every W milliseconds, where W is wait. Calls over W get dropped
There is a library suited for this purpose, it's Backburner.js from Ember.
https://github.com/BackburnerJS/
You'd use it so.
var backburner = new Backburner(["task"]); //You need a name for your tasks
function saySomething(words) {
backburner.throttle("task", console.log.bind(console, words)
}, 1000);
}
function mainTask() {
"This will be said with a throttle of 1 second per word!".split(' ').map(saySomething);
}
backburner.run(mainTask)
Here's my own version of Vikas post:
throttle: function (callback, limit, time) {
var calledCount = 0;
var timeout = null;
return function () {
if (limit > calledCount) {
calledCount++;
callback();
}
if (!timeout) {
timeout = setTimeout(function () {
calledCount = 0
timeout = null;
}, time);
}
};
}
I find that using setInterval
is not a good idea.
This throttle function is build on ES6. Callback functions takes arguments (args), and still it works wrapped with throttle function. Be free to customize delay time according to your app needs. 1 time per 100ms is used for development mode, event "oninput" is just an example for frequent case of its use:
const callback = (...args) => {
console.count('callback throttled with arguments:', args);
};
throttle = (callback, limit) => {
let timeoutHandler = 'null'
return (...args) => {
if (timeoutHandler === 'null') {
timeoutHandler = setTimeout(() => {
callback(...args)
timeoutHandler = 'null'
}, limit)
}
}
}
window.addEventListener('oninput', throttle(callback, 100));
P.S. As @Anshul explained: throttling enforces a maximum number of times a function can be called over time. As in "execute this function at most once every 100 milliseconds."
Below is the simplest throttle I could think of, in 13 LOC. It creates a timeout each time the function is called and cancels the old one. The original function is called with the proper context and arguments, as expected.
function throttle(fn, delay) {
var timeout = null;
return function throttledFn() {
window.clearTimeout(timeout);
var ctx = this;
var args = Array.prototype.slice.call(arguments);
timeout = window.setTimeout(function callThrottledFn() {
fn.apply(ctx, args);
}, delay);
}
}
// try it out!
window.addEventListener('resize', throttle(function() {
console.log('resize!!');
}, 200));
In below example, try clicking the button multiple times, but the myFunc
function would be executed only once in 3 sec.
The function throttle
is passed with the function to be executed and the delay.It returns a closure, which is stored in obj.throttleFunc
.
Now since obj.throttleFunc
stores a closure, the value of isRunning
is maintained inside it.
function throttle(func, delay) {
let isRunning;
return function(...args) {
let context = this; // store the context of the object that owns this function
if(!isRunning) {
isRunning = true;
func.apply(context,args) // execute the function with the context of the object that owns it
setTimeout(function() {
isRunning = false;
}, delay);
}
}
}
function myFunc(param) {
console.log(`Called ${this.name} at ${param}th second`);
}
let obj = {
name: "THROTTLED FUNCTION ",
throttleFunc: throttle(myFunc, 3000)
}
function handleClick() {
obj.throttleFunc(new Date().getSeconds());
}
button {
width: 100px;
height: 50px;
font-size: 20px;
}
<button onclick="handleClick()">Click me</button>
If we don't want the context or arguments to be passed, then a simpler version of this would be as following:
function throttle(func, delay) {
let isRunning;
return function() {
if(!isRunning) {
isRunning = true;
func()
setTimeout(function() {
isRunning = false;
}, delay);
}
}
}
function myFunc() {
console.log('Called');
}
let throttleFunc = throttle(myFunc, 3000);
function handleClick() {
throttleFunc();
}
button {
width: 100px;
height: 50px;
font-size: 20px;
}
<button onclick="handleClick()">Click me</button>
I also want to suggest a simple solution for when there is only 1 function you know you will call (for example: Search)
here is what i did in my project
let throttle;
function search() {
if (throttle) {
clearTimeout(throttle);
}
throttle = setTimeout(() => {
sendSearchReq(str)
}, 500);
}
Search is called on input change event
One does not need a ton of local variables for a decent throttle function. The purpose of a throttle function is to reduce browser resources, not to apply so much overhead that you are using even more. As proof of evidence of this claim, I have devised a throttle function that has only 4 "hanging" variables in its scope. (A "hanging" variable is a variable that is never garbage collected because it always remains referenced by a function that could potentially be called, thereby soaking up memory.) A handful of throttle functions usually do not ever do any harm; but, if there are thousands of throttled functions, then memory starts to become scarce if you use a really inefficient throttle function. My solution is below.
var timenow = self.performance?performance.now.bind(performance):Date.now;
function throttle(func, alternateFunc, minInterval) {
var lastTimeWent = -1;
return function() {
var newTimeWent = timenow();
if ((newTimeWent-lastTimeWent) > minInterval) {
lastTimeWent = newTimeWent;
return func.apply(this, arguments);
} else if (typeof alternateFunc === "function")
return alternateFunc.apply(this, arguments);
};
}
Then, to wrap this throttle function around EventTarget for things like DOM clicks, window events, XMLHttpRequests onprogress, FileReader onprogress, [etc.], like so:
var tfCache = []; // throttled functions cache
function listen(source, eventName, func, _opts){
var i = 0, Len = tfCache.length, cF = null, options = _opts || {};
a: {
for (; i < Len; i += 4)
if (tfCache[i] === func &&
tfCache[i+1] === (options.ALTERNATE||null) &&
tfCache[i+2] === (options.INTERVAL||200)
) break a;
cF = throttle(func, options.ALTERNATE||null, options.INTERVAL||200);
tfCache.push(func, options.ALTERNATE||null, options.INTERVAL||200, cF);
}
source.addEventListener(eventName, cF || tfCache[i+3], _opts);
return cF === null; // return whether it used the cache or not
};
function mute(source, eventName, func, _opts){
var options = _opts || {};
for (var i = 0, Len = tfCache.length; i < Len; i += 4)
if (tfCache[i] === func &&
tfCache[i+1] === (options.ALTERNATE||null) &&
tfCache[i+2] === (options.INTERVAL||200)
) {
source.removeEventListener(eventName, tfCache[i+3], options);
return true;
}
return false;
}
Example usage:
(function(){"use strict";
// The function throttler //
var timenow = self.performance?performance.now.bind(performance):Date.now;
function throttle(func, alternateFunc, minInterval) {
var lastTimeWent = -1;
return function() {
var newTimeWent = timenow();
if ((newTimeWent-lastTimeWent) > minInterval) {
lastTimeWent = newTimeWent;
return func.apply(this, arguments);
} else if (typeof alternateFunc === "function")
return alternateFunc.apply(this, arguments);
};
}
// The EventTarget wrapper: //
var tfCache = []; // throttled functions cache
function listen(source, eventName, func, _opts){
var i = 0, Len = tfCache.length, cF = null, options = _opts || {};
a: {
for (; i < Len; i += 4)
if (tfCache[i] === func &&
tfCache[i+1] === (options.ALTERNATE||null) &&
tfCache[i+2] === (options.INTERVAL||200)
) break a;
cF = throttle(func, options.ALTERNATE||null, options.INTERVAL||200);
tfCache.push(func, options.ALTERNATE||null, options.INTERVAL||200, cF);
}
source.addEventListener(eventName, cF || tfCache[i+3], _opts);
return cF === null; // return whether it used the cache or not
};
function mute(source, eventName, func, _opts){
var options = _opts || {};
for (var i = 0, Len = tfCache.length; i < Len; i += 4)
if (tfCache[i] === func &&
tfCache[i+1] === (options.ALTERNATE||null) &&
tfCache[i+2] === (options.INTERVAL||200)
) {
source.removeEventListener(eventName, tfCache[i+3], options);
return true;
}
return false;
}
// Finally, the key logger: //
var keysEle = document.getElementById("keyspressed");
var recordKeyStroke = function(dir,color){return function listener(Evt){
if (Evt.key=="e") { mute(document, 'keydown', listener, downOptions);
mute(document, 'keyup', listener, upOptions); }
if (!Evt.repeat) keysEle.insertAdjacentHTML(
"afterbegin",
'<div class="'+(Evt.key=="e"?"red":color)+'">'+dir+Evt.key+'</div>'
);
}};
var downOptions = {passive:1, ALTERNATE: recordKeyStroke("+","grey") };
listen(document, 'keydown', recordKeyStroke("+","green"), downOptions);
var upOptions = {passive:1, ALTERNATE: recordKeyStroke("-","grey") };
listen(document, 'keyup', recordKeyStroke("-","green"), upOptions);
})();
The keys you press and release are shown below.
Those in grey are ones which were passed up by the throttle.
Those in green are ones that were recorded by the throttle.
When the "e" key is pressed, it is red and keystrokes are no longer recorded.
"+[key]" = keydown and "-[key]" = keyup.
<div id="keyspressed" style="white-space:pre-wrap;font-family:'Roboto Mono',monospace;"></div>
<style>.red{color:#f77}.green{color:#5f5}.grey{color:#aaa}</style>
EDIT: Stackoverflow does not allow code snippets on lowly voted answers to be runned ):. Please see this JSFiddle in order to run the code.
By default, this throttles the function to at most one call every 200ms. To change the interval to a different number of milliseconds, then pass a the optionsObject.INTERVAL
option in the options argument and set it to the desired minimum milliseconds between executions. (Since timers are not always the most precise,) If you have an exact minimum interval desired, then I would recommend that you subtract one or two from desired optionsObject.INTERVAL
to ensure that it always gets executed at least when it should. If you need to do something to the arguments to the throttled func when the throttled function's execution is delayed (due to excess calls), then use the optionsObject.ALTERNATE
option. This "ALTERNATE" is a function that gets called immediately in place of the primary function whenever the call to the primary function is dropped. For example, if you are using your throttled function on an EventTarget, but want to preventDefault()
on dropped events, then use {ALTERNATE: function(evt){ evt.preventDefault(); }}
for the options object.
I would use the underscore.js or lodash source code to find a well tested version of this function.
Here is the slightly modified version of the underscore code to remove all references to underscore.js himself:
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
function throttle(func, wait, options) {
var context, args, result;
var timeout = null;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
var now = Date.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
};
Please note that this code can be simplified if you don't need all the options that underscore support.
Edit 1: Removed another reference to underscore, thx to Zettam's comment
Edit 2: Added suggestion about lodash and possible code simplification, thx to lolzery wowzery's comment
What about this?
function throttle(func, timeFrame) {
var lastTime = 0;
return function () {
var now = new Date();
if (now - lastTime >= timeFrame) {
func();
lastTime = now;
}
};
}
Simple.
You may be interested in having a look at the source.
function throttle(targetFunc, delay){
let lastFunc;
let lastTime;
return function(){
const _this = this;
const args = arguments;
if(!lastTime){
targetFunc.apply(_this, args);
lastTime = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function(){
targetFunc.apply(_this, args);
lastTime = Date.now();
}, delay - (Date.now() - lastTime));
}
}
}
Try it :
window.addEventListener('resize', throttle(function() {
console.log('resize!!');
}, 200));
Simple throttle function -
Note- Keep on clicking on the button , You'll see console log at first on click and then only after every 5 seconds until you're keep clicking.
HTML -
<button id='myid'>Click me</button>
Javascript -
const throttle = (fn, delay) => {
let lastTime = 0;
return (...args) => {
const currentTime = new Date().getTime();
if((currentTime - lastTime) < delay) {
return;
};
lastTime = currentTime;
return fn(...args);
}
};
document.getElementById('myid').addEventListener('click', throttle((e) => {
console.log('I am clicked');
}, 5000));
We can also implement using a flag-
var expensive = function(){
console.log("expensive functionnns");
}
window.addEventListener("resize", throttle(expensive, 500))
function throttle(expensiveFun, limit){
let flag = true;
return function(){
let context = this;
let args = arguments;
if(flag){
expensiveFun.apply(context, args);
flag = false;
setTimeout(function(){
flag = true;
}, limit);
}
}
}
©2020 All rights reserved.