My throttle function is not waiting the limit time - javascript

I'm learning throttling and I'm having an issue where my throttle method is not waiting the limit time to run.
const display = (msg) => {
console.log(msg). // I know this function does not do anything, but I'm trying to understand how I can call a function inside my throttle.
}
const throttle = (func, limit) => {
let flag = true;
return function() {
if(flag) {
func.apply(this, arguments);
flag = false;
setTimeout(() => flag = true, limit);
}
}
}
const throttleDisplay = () => {
return throttle(display("Hi"), 6000);
}
for(let i=1; i<=10; i++) {
setTimeout(throttleDisplay, i*1000);
}
My output is "Hi" 10 times, but I shouldn't have 10 times Hi because I have a 6s wait between one call and another.

throttle takes a callback as a parameter, but you're invoking display immediately.
const throttleDisplay = () => {
return throttle(display("Hi"), 6000);
}
is exactly equivalent to
const throttleDisplay = () => {
const result = display("Hi");
return throttle(result, 6000);
}
See the problem?
You need a function that invokes display with the argument you want instead:
const throttleDisplay = () => {
return throttle(() => display("Hi"), 6000);
}

Related

Problem with a for loop with countdown in node.js

I have the following code and I have tried everything, but I can't find the solution, I hope someone can help me.
So far the code executes the fRoll.roll, enters the FOR loop and executes the countdown of the EventEmitter, at the end it executes the fRoll.roll again, but the For loop is executed without respecting the time of the setInterval, which corresponds to the startTimer () of the EventEmitter.
My question is how can I make between each iteration of the loop wait for the setInterval of the EventEmitter and then execute the listeners.
const appGlobal = async (page, website) => {
const { EventEmitter } = require('events');
try {
class CountDownRoll extends EventEmitter {
constructor(CountDownRollTime) {
super();
this.CountDownRollTime = CountDownRollTime;
this.currentTime = 0;
}
startTimer() {
const timer = setInterval(() => {
this.currentTime++;
this.emit('update', this.currentTime);
// Check if CountDownRoll has reached to the end
if (this.currentTime === this.CountDownRollTime) {
clearInterval(timer);
this.emit('roll');
}
// Check if CountDownRoll will end in 10 minutes
if (this.currentTime === this.CountDownRollTime - 600) {
this.emit('end-soon');
}
}, 1000);
}
}
//======================================================
// this code is executed
const fRoll = require('../task/roll')(page, website)
fRoll.roll
//======================================================
// I need this loop to repeat itself X number of times and in each iteration,
// wait for the countdown before executing the codes.
const count = 10
for (let start = 1; start < count; start++)
const myCountDownRoll = new CountDownRoll(3600);
// Countdown
myCountDownRoll.on('update', (timer) => {
console.log(`${timer} seconds has been passed since the timer started`);
// code that performs at the end of the countdown
myCountDownRoll.on('roll', () => {
console.log('✓ CountDownRoll is completed');
//======================================================
// this code is executed
const fRoll = require('../task/roll')(page, website)
fRoll.roll
//======================================================
});
// Code that performs when reaching a specific time of the countdown
myCountDownRoll.on('end-soon', () => {
console.log('✓ Count down will be end in 600 seconds');
// Code to execute here
});
myCountDownRoll.startTimer();
}
} catch (e) {
console.log('Error intro app-global:', e)
}
}
module.exports = appGlobal
What happens to me is that it is executed 10 times for each time the listener of the countdown event is executed.
I have redone the code for a sleep function, as I have not found a way to use thestartTimer ()of the EventEmitter as a condition for each iteration of the loop.
const appGlobal = async (page, website) => {
try {
async function sleep(sec) {
return new Promise((resolve) => {
setTimeout(function () {
resolve()
}, sec * 1000)
})
}
async function roll() {
let count = 10000
for (let start = 1; start < count; start++) {
// We call the roll
const freeRoll = require('../task/roll')(page, website)
freeRoll.roll
let random = Math.round(Math.random() * 8)
console.log("Number is:" + " " + random)
if (random === 3) {
// We call the wine
const fwine = require('../task/wine')(page, website)
fwine.wine
await sleep(3600)
} else {
await sleep(3600)
}
}
}
roll();
} catch (e) {
console.log('Error intro app-global:', e)
}
}
module.exports = appGlobal

How to call an api for status messages of other api calls progress

I am trying to make two api calls parallely. When X is getting called, Y should go paralleley and it will call recursively itself until api X resolves.
two Calls fires immediately clicking a button
function X () {
return new Promise((resolve,reject) => {
Service.requestAPI(RequestMethod.GET, API_URL.validateProductStatus)
.then(response => {
resolve(response)
}
}
}
function Y () {
Service.requestAPI(RequestMethod.GET, API_URL.validateProductStatus)
.then(response => {
if(response){
setTimeout(() => {
this.onStatusChange()
},1000)
}
}
}
async function buttonCLick() {
const XResponse = await X();
Y();
}
The above solution doesnt work as expected. The second call Y is becoming sync call instead of async.
Where am i going wrong?
That's what await does, it waits for a async call to complete before resuming.
A lot of code is lacking from your sample to provide a solution specific to your use case. But in general, to do async processing while updating dynamic status, you'll need either callbacks or a stream.
let sleep = ms => new Promise(r => setTimeout(r, ms));
let doAsyncWork = async onProgress => {
let n = 100;
for (let i = 0; i < n; i++) {
await sleep(Math.random() * 100);
onProgress(i / n);
}
onProgress(1);
};
let updateProgress = progress => {
document.querySelector('div').textContent = progress < 1 ?
`Working, ${progress * 100}%` :
`Complete!`;
};
doAsyncWork(updateProgress);
<div></div>
await will block the execution until X is resolved, one way you can achieve this is by using a global variable that determines whether to call Y or not :
let callY = true;
const x = () => {
return new Promise((resolve, reject) => {
// some api call
setTimeout(() => resolve("X is reolved"), 3000);
});
};
const y = () => {
if (callY) {
console.log("waiting for x");
// some api call
// once it resolves based on the response doing a recursive call
setTimeout(() => y(), 1000); // calling the function after 3 seconds
}
};
async function buttonCLick() {
x().then(res => {
console.log(res);
callY = false;
});
y();
}
buttonCLick();

Sequentially execute the functions? Not sure how to precisely describe the question

I run into a problem trying to solve it; but somehow stuck in the middle. The problem is to implement a function $do() which could take any number of functions and execute them in the order they were called.
In the specific example, print a in 700 ms and b in 200, then print c in 100ms. The challenge part is although c should be print first, but it is called after in the $do() after a and b get called.
My implementation as below, almost working, but cannot print c for some reason. Help needed. Thanks a lot.
const a = (cb) => {
setTimeout(() => {
cb();
console.log('a', 700);
}, 700);
};
const b = (cb) => {
setTimeout(() => {
cb();
console.log('b', 200);
}, 200);
};
const c = (cb) => {
setTimeout(() => {
cb();
console.log('c', 100);
}, 100);
};
const stores = [];
let running = false;
function $do() {
const cbs = Array.prototype.slice.call(arguments);
stores.push(cbs);
let i = 0;
while (stores.length > 0 && !running) {
let head = stores.shift();
running = i < head.length;
head.forEach((cb) => {
cb(() => {
i++;
running = i < head.length;
console.log(running);
});
});
}
}
$do(a, b);
$do(c);
This solution uses a closure and Promise.all() to wait for each of the previous pending invocations of $do() to complete before calling each of the functions provided in the current invocation.
This means that c will not be called until both a and b have called back:
const delay = (name, ms) => cb => {
setTimeout(() => {
cb()
console.log(name, ms)
}, ms)
}
const a = delay('a', 700)
const b = delay('b', 200)
const c = delay('c', 100)
const $do = (() => {
let done = Promise.resolve()
return (...fns) => {
done = done.then(
() => Promise.all(fns.map(fn => new Promise(fn)))
)
}
})()
$do(a, b)
$do(c)
Using an IIFE below only to avoid polluting the global namespace unnecessarily
One thing that I'm not sure of is pushing an array onto stores - if there were no "processing" the array would end up
[[a, b],[c]]
and the processing you require would be a, then b, then c - so stores could just as easily be
[a, b, c]
Which is what the code below does, by the way
const a = (cb) => {
setTimeout(() => {
cb();
console.log('a', 700);
}, 700);
};
const b = (cb) => {
setTimeout(() => {
cb();
console.log('b', 200);
}, 200);
};
const c = (cb) => {
setTimeout(() => {
cb();
console.log('c', 100);
}, 100);
};
// NOTE: I only use the IIFE to not polute GLOBAL namespace with stores and running variables
const $do = (() => {
const stores = [];
let running = false;
let process = () => {
const cb = stores.shift();
cb(() => {
running = !!stores.length;
if (running) {
process();
}
});
};
return (...cbs) => {
stores.push(...cbs);
if (!running) {
running = true;
process();
}
};
})();
$do(a, b);
$do(c);

Selective timeout based events handling: immediate first, debounce next

Let's say there are random sequences of external actions (e.g. scroll events). I need to handle the first action immediately, then dismiss all actions occurred with intervals less than some given delta, and then handle the next one which should be delayed for that delta. Further actions should be processed in the same manner.
This looks like a combination of debounce-immediate and simple debounce. I prepared a diagram to demonstrate the idea.
What is the best solution/approach here? I wonder if there is some ready-made pattern...
UPDATE
I would like to thank all participants! For the research I created plunker with four five different realizations suggested in answers: https://plnkr.co/N9nAwQ.
const handler = [
processEvent, // normal
debounceNext(processEvent, DELAY), // dhilt
makeRateLimitedEventHandler(DELAY, processEvent), // user650881
debounceWithDelay(processEvent, DELAY, 0), // willem-dhaeseleer
_.debounce(processEvent, DELAY, {leading: true}) // lodash debounce + leading,
debounceish(DELAY, processEvent) //Mikk3lRo
];
A great news was the Lodash has a leading-flag debounce implementation which satisfies the issue (thanks to Willem D'Haeseleer). And here is the cool demo from Mikk3lRo' answer, he also provided some useful synthesis.
I investigated the sources and the results: form just visual point to memory allocation stuff... I didn't find any performance issues, and the views seem okey. So the ultima ratio was the code itself. All sources were converted to ES6 (as you can see in Plunker) for I can compare them fully. I excluded my own try (it is a bit excessive, despite I like how it looks). The timestamp version is very interesting! The postDelay version's nice, though it wasn't a requested feature (so that snippet demo has double delay for two lodash demos).
I decided not to have a lodash dependency (in other way I certainly would use lodash debounce with leading option), so I chose debounceish by Mikk3lRo.
PS I would like to share that little bounty (unfortunately there is no such an option) or even take some more scores from my reputation for it (but not 200, is too much and would be unfair to the winner which would have only 100). I even can't vote twice... Nevermind.
A very simple solution in vanilla JS using a single timer:
function debounceish(delta, fn) {
var timer = null;
return function(e) {
if (timer === null) {
//Do now
fn(e);
//Set timer that does nothing (but is not null until it's done!)
timer = setTimeout(function(){
timer = null;
}, delta);
} else {
//Clear existing timer
clearTimeout(timer);
//Set a new one that actually does something
timer = setTimeout(function(){
fn(e);
//Set timer that does nothing again
timer = setTimeout(function(){
timer = null;
}, delta);
}, delta);
}
};
}
function markEvt(e) {
var elm = document.createElement('div');
elm.style.cssText = 'position:absolute;background:tomato;border-radius:3px;width:6px;height:6px;margin:-3px;';
elm.style.top = e.clientY + 'px';
elm.style.left = e.clientX + 'px';
document.body.appendChild(elm);
}
document.addEventListener('click', debounceish(2000, markEvt));
<p>Click somewhere (2000ms delta) !</p>
Comparing 6 proposals using the same type of visualization:
var methods = {
default: function(delay, fn) {
return fn;
},
dhilt_debounceNext: (delay, cb) => {
let timer = null;
let next = null;
const runTimer = (delay, event) => {
timer = setTimeout(() => {
timer = null;
if(next) {
next(event);
next = null;
runTimer(delay);
}
}, delay);
};
return (event) => {
if(!timer) {
cb(event);
}
else {
next = cb;
clearTimeout(timer);
}
runTimer(delay, event);
}
},
Mikk3lRo_debounceish(delta, fn) {
var timer = null;
return function(e) {
if (timer === null) {
//Do now
fn(e);
//Set timer that does nothing (but is not null until it's done!)
timer = setTimeout(function(){
timer = null;
}, delta);
} else {
//Clear existing timer
clearTimeout(timer);
//Set a new one that actually does something
timer = setTimeout(function(){
fn(e);
//Set timer that does nothing again
timer = setTimeout(function(){
timer = null;
}, delta);
}, delta);
}
};
},
user650881_makeRateLimitedEventHandler: function(delta_ms, processEvent) {
var timeoutId = 0; // valid timeoutId's are positive.
var lastEventTimestamp = 0;
var handler = function (evt) {
// Any untriggered handler will be discarded.
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = 0;
}
var curTime = Date.now();
if (curTime < lastEventTimestamp + delta_ms) {
// within delta of last event, postpone handling
timeoutId = setTimeout(function () {
processEvent(evt);
}, delta_ms);
} else {
// long enough since last event, handle now
processEvent(evt);
}
// Set lastEventTimestamp to time of last event after delta test.
lastEventTimestamp = Date.now();
};
return handler;
},
Willem_DHaeseleer_debounceWithDelay: (delay, func) => {
let postDebounceWait;
let timeOutLeading = false;
const debounced = _.debounce((...args) => {
// wrap the handler so we can add an additional timeout to the debounce invocation
if (timeOutLeading) {
/*
for the first invocation we do not want an additional timeout.
We can know this is the leading invocation because,
we set timeOutLeading immediately to false after invoking the debounced function.
This only works because the debounced leading functionality is synchronous it self.
( aka it does not use a trampoline )
*/
func(...args);
} else {
postDebounceWait = setTimeout(() => {
func(...args)
}, delay);
}
}, delay, {leading: true});
return (...args) => {
// wrap the debounced method it self so we can cancel the post delay timer that was invoked by debounced on each invocation.
timeOutLeading = true;
clearTimeout(postDebounceWait);
debounced(...args);
timeOutLeading = false;
}
},
Willem_DHaeseleer_lodashWithLeading: (delta, cb) => {
return _.debounce(cb, delta * 2, {leading: true});
},
Javier_Rey_selfCancelerEventListener: function (delta, fn) {
return function(ev) {
var time = new Date().getTime();
if (ev.target.time && time - ev.target.time < delta) {return;}
ev.target.time = time;
fn(ev);
};
},
};
var method_count = 0;
var colors = ['grey', 'tomato', 'green', 'blue', 'red', 'orange', 'yellow', 'black'];
function markEvt(method) {
var style = 'position:absolute;border-radius:3px;width:6px;height:6px;margin:-3px;';
style += 'background:' + colors[method_count] + ';';
if (method_count > 0) {
style += 'transform:rotate(' + Math.floor(360 * method_count / (Object.keys(methods).length - 1)) + 'deg) translateY(-8px);';
}
var elm = document.createElement('div');
elm.innerHTML = '<span style="width:.8em;height:.8em;border-radius:.4em;display:inline-block;background:' + colors[method_count] + '"></span> ' + method;
document.body.appendChild(elm);
method_count++;
return function(e) {
elm = document.createElement('div');
elm.style.cssText = style;
elm.style.top = e.clientY + 'px';
elm.style.left = e.clientX + 'px';
document.body.appendChild(elm);
};
}
for (var method in methods) {
document.addEventListener('click', methods[method](2000, markEvt(method)));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Note that I needed to make minor adjustments to some of the methods to get a common interface. Adapting Cully's answer took more effort than I was willing to put in considering the comments suggest it doesn't do what the OP wants anyway.
It should be pretty clear that Javier Rey's approach behaves completely differently from the rest. Dhilt, user650881 and my own methods seem consistent. Both of Willem D'Haeseleer's methods have double the delay (and other subtle differences), but seem to behave consistently too. As far as I understand the double delay is completely intentional, though that is not how I understand the OP.
I would say that Willem D'Haeseleer's lodash method is without a doubt the simplest - if you already use lodash that is. Without external dependencies my method is IMO simplest - but I may be biased on that one ;)
You might track the last event time and only create a timer event when a follow-up check is required.
function makeRateLimitedEventHandler(delta_ms, processEvent) {
var timeoutId = 0; // valid timeoutId's are positive.
var lastEventTimestamp = 0;
var handler = function (evt) {
// Any untriggered handler will be discarded.
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = 0;
}
var curTime = Date.now();
if (curTime < lastEventTimestamp + delta_ms) {
// within delta of last event, postpone handling
timeoutId = setTimeout(function () {
processEvent(evt);
}, delta_ms);
} else {
// long enough since last event, handle now
processEvent(evt);
}
// Set lastEventTimestamp to time of last event after delta test.
lastEventTimestamp = Date.now();
};
return handler;
}
var DELTA_MS = 5000;
var processEvent = function (evt) { console.log('handling event'); };
el.addEventHandler('some-event', makeRateLimitedEventHandler(DELTA_MS, processEvent));
The behavior in your visual is no different then the standard lodash debouncing behavior with the leading option, the only difference is that your only displaying half of the delta instead of the full delta.
Therefore, your solution can be as simple as this.
_.debounce(cb, delta * 2, {leading: true});
https://lodash.com/docs/4.17.4#debounce
If you want the last delay to be longer, you can solve that by wrapping both the debounced method and the handler. That way you can set the timeout in the handler, and cancel it in the debounce wrapper.
You do have to check if the current invocation was the leading one in order to not add the timeout in that case.
It could look like this:
const _ = require('lodash');
const bb = require('bluebird');
function handler(arg) {
console.log(arg, new Date().getSeconds());
}
const debounceWithDelay = (func, delay, postDelay) => {
let postDebounceWait;
let timeOutLeading = false;
const debounced = _.debounce((...args) => {
// wrap the handler so we can add an additional timeout to the debounce invocation
if (timeOutLeading) {
/*
for the first invocation we do not want an additional timeout.
We can know this is the leading invocation because,
we set timeOutLeading immediately to false after invoking the debounced function.
This only works because the debounced leading functionality is synchronous it self.
( aka it does not use a trampoline )
*/
func(...args);
} else {
postDebounceWait = setTimeout(() => {
func(...args)
}, postDelay);
}
}, delay, {leading: true});
return (...args) => {
// wrap the debounced method it self so we can cancel the post delay timer that was invoked by debounced on each invocation.
timeOutLeading = true;
clearTimeout(postDebounceWait);
debounced(...args);
timeOutLeading = false;
}
};
const debounceDelay = debounceWithDelay(handler, 50, 2000);
(async function () {
console.log(new Date().getSeconds());
debounceDelay(1);
debounceDelay(2);
debounceDelay(3);
debounceDelay(4);
await bb.delay(3000);
debounceDelay(5);
await bb.delay(3000);
debounceDelay(6);
debounceDelay(7);
debounceDelay(8);
})();
Runnable script:
Here's something that I think works the way you described. If not, it's at least something to go off of.
// set up the event bus
const start = getMilli()
const bus = createBus()
bus.on('event', e => console.log(`[${getPassage(start)}] [${e}] original bus: saw event`))
const wrappedBus = wrapBus(1600, 'event', bus)
wrappedBus.on('event', e => console.log(`[${getPassage(start)}] [${e}] wrapped bus: saw event`))
wrappedBus.on('skipped', e => console.log(`[${getPassage(start)}] [${e}] skipped by wrapped bus`))
wrappedBus.on('last before interval', e => console.log(`[${getPassage(start)}] [${e}] this was the last event before the end of the interval`))
wrappedBus.on('interval tick', _ => console.log(`[${getPassage(start)}] interval tick`))
// trigger events on the bus every so often
let totalTime = 0
const intervalTime = 300
setInterval(() => {
totalTime += intervalTime
bus.trigger('event', totalTime)
}, intervalTime)
function getMilli() {
return (new Date()).getTime()
}
function getPassage(from) {
return getMilli() - from
}
// creates a simple event bus
function createBus() {
const cbs = {}
return {
on: (label, cb) => {
if(cbs.hasOwnProperty(label)) cbs[label].push(cb)
else cbs[label] = [cb]
},
trigger: (label, e) => {
if(cbs.hasOwnProperty(label)) cbs[label].forEach(f => f(e))
},
}
}
// creates a new bus that should trigger the way you described
function wrapBus(waitInterval, eventLabel, bus) {
const newBus = createBus()
let deliveredFirst = false
let gotIgnoredEvent = false
let lastIgnoredEvent = undefined
setInterval(() => {
// just here so we know when this interval timer is ticking
newBus.trigger('interval tick', null)
// push the last event before the end of this interval
if(gotIgnoredEvent) {
gotIgnoredEvent = false
deliveredFirst = false
newBus.trigger(eventLabel, lastIgnoredEvent)
newBus.trigger('last before interval', lastIgnoredEvent)
}
}, waitInterval)
bus.on(eventLabel, function(e) {
if(!deliveredFirst) {
newBus.trigger(eventLabel, e)
deliveredFirst = true
gotIgnoredEvent = false
}
else {
gotIgnoredEvent = true
lastIgnoredEvent = e
// this is here just to see when the wrapped bus skipped events
newBus.trigger('skipped', e)
}
})
return newBus
}
Here's my try:
const debounceNext = (cb, delay) => {
let timer = null;
let next = null;
const runTimer = (delay, event) => {
timer = setTimeout(() => {
timer = null;
if(next) {
next(event);
next = null;
runTimer(delay);
}
}, delay);
};
return (event) => {
if(!timer) {
cb(event);
}
else {
next = cb;
clearTimeout(timer);
}
runTimer(delay, event);
}
};
const processEvent = (event) => console.log(event);
const debouncedHandler = debounceNext(processEvent, 125);
myElement.addEventListener('scroll', debouncedHandler);

Simple throttle in JavaScript

I am looking for a simple throttle in JavaScript. 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.
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 itself:
// 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.
Please find below a very simple and non-configurable version of this function:
function throttle (callback, limit) {
var waiting = false; // Initially, we're not waiting
return function () { // We return a throttled function
if (!waiting) { // If we're not waiting
callback.apply(this, arguments); // Execute users function
waiting = true; // Prevent future invocations
setTimeout(function () { // After a period of time
waiting = false; // And allow future invocations
}, limit);
}
}
}
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
Edit 3: Due to popular requests, I added a very simple, non-configurable version of the function, adapted from #vsync 's comment
What about this?
function throttle(func, timeFrame) {
var lastTime = 0;
return function () {
var now = Date.now();
if (now - lastTime >= timeFrame) {
func();
lastTime = now;
}
};
}
Simple.
You may be interested in having a look at the source.
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);
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.
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();
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();
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>
JSFiddle
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 seen a lot of answers here that are way too complex for "a simple throttle in js".
Almost all of the simpler answers just ignore calls made "in throttle" instead of delaying execution to the next interval.
Here's a simple implementation that also handles calls "in throttle":
const throttle = (func, limit) => {
let lastFunc;
let lastRan = Date.now() - (limit + 1); //enforces a negative value on first run
return function(...args) {
const context = this;
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
func.apply(context, args);
lastRan = Date.now();
}, limit - (Date.now() - lastRan)); //negative values execute immediately
}
}
This is almost the exact same implementation for a simple debounce. It just adds a calculation for the timeout delay which requires tracking when the function was last ran. See below:
const debounce = (func, limit) => {
let lastFunc;
return function(...args) {
const context = this;
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
func.apply(context, args)
}, limit); //no calc here, just use limit
}
}
Simple solution in ES6. Codepen Demo
const handleOnClick = () => {
console.log("hello")
}
const throttle = (func, delay) => {
let timeout = null;
return function (...args) {
if (timeout === null) {
func.apply(this, args);
timeout = setTimeout(() => {
timeout = null;
}, delay)
}
}
}
document.querySelector("#button").addEventListener("click", throttle(handleOnClick, 500))
<button type="button" id="button">Click me</button>
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.
With leading and trailing invocations:
const throttle = (fn, ms) => {
let locked = false
return function () {
if (!locked) {
locked = true
fn.apply(this, arguments)
setTimeout(() => {
fn.apply(this, arguments)
locked = false
}, ms)
}
}
}
Test case:
function log({ gender, address }) {
console.log({
name: this.name,
gender,
address,
})
}
const jack = {
name: 'Jack',
log: throttle(log, 3000),
}
Array.from({ length: 5 }, () => jack.log({ gender: 'Male', address: 'LA' }))
I made a npm package with some throttling functions:
npm install function-throttler
throttleAndQueue
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
throttledUpdate
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)
throttle
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)
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."
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
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));
CodeSandbox
const { now } = Date;
export default function throttle(func, frameDuration) {
let timeout = null;
let latest;
const epoch = now();
function getDurationToNextFrame() {
const elapsed = now() - epoch;
const durationSinceLastFrame = elapsed % frameDuration;
return frameDuration - durationSinceLastFrame;
}
function throttled(...args) {
latest = () => {
func.apply(this, args);
};
if (!timeout) {
timeout = setTimeout(() => {
latest();
timeout = null;
}, getDurationToNextFrame());
}
}
return throttled;
}
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);
}
}
}
Here is a bit modernized and simplified version of #clément-prévost answer
function throttle(func, wait, options = {}) {
let timeout = null;
let previous = 0;
const later = (...args) => {
previous = options.leading === false ? 0 : Date.now();
func(...args);
};
return (...args) => {
const now = Date.now();
if (!previous && options.leading === false) {
previous = now;
}
const remaining = wait - (now - previous);
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func(...args);
} else if (options.trailing !== false) {
clearTimeout(timeout);
timeout = setTimeout(() => later(...args), remaining);
}
};
}
function myFunc(a) {
console.log(`Log: ${a} ${this.val}`);
}
const myFuncThrottled = throttle(myFunc.bind({val: 42}), 1234, {leading: true, trailing: true})
myFuncThrottled(1)
myFuncThrottled(2)
myFuncThrottled(3)
function throttle(CB,ms=300,Id='Identifier for the callback(CB)'){
Id = Id || ""+CB
var N = throttle.N = throttle.N || {}; // Static variable N to store all callbacks ids and their status
if( N[Id] ) return; // already in the queue to run
N[Id] = 1; // add it the queue
setTimeout(()=>{
N[Id] = 0; // remove it from the queue
CB(); // finally call the function
}, ms);
}
for(var i=0;i<100;i++){
throttle(e=>console.log("Hi1"),1e3,'F1');
}
// will only output : Hi1
// this function guarantee the callback to run at least once
Some great solutions here already, but I was looking for a modern version with trailing (and optionally leading) executions, with the last passed arguments provided to each function call:
const throttle = (fn, wait=500, leading=true) => {
let prev, timeout, lastargs;
return (...args) => {
lastargs = args;
if (timeout) return;
timeout = setTimeout(() => {
timeout = null;
prev = Date.now();
// let's do this ... we'll release the stored args as we pass them through
fn.apply(this, lastargs.splice(0, lastargs.length));
// some fancy timing logic to allow leading / sub-offset waiting periods
}, leading ? prev && Math.max(0, wait - Date.now() + prev) || 0 : wait);
};
}
Usage:
x = throttle((...args) => console.log(...args));
let n = 0;
x(++n, 'boom');
x(++n, 'boom');
x(++n, 'boom');
if there will be more than one function defining them one by one would not be maintainable so i would suggest use a helper class to keep values for each
class slowDown {
constructor(cb,timeGap){
this.last = 0
this.run = function(){
let current = Date.now(),
shouldRun = (current - this.last) >= timeGap
if(shouldRun){
cb(current - this.last)
this.last = current
}
}
}
}
// example use
const press = new slowDown(timeElapsed => {
// define function here which you wanted to slow down
console.log("pressed after " + timeElapsed + " ms")
},750)
window.addEventListener("keydown",()=>{
press.run()
})
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));

Categories

Resources