Implement debounce: how to make three invocations result in one effective call? - javascript

How can I invoke three times a function with a setTimeOut but just print it once after 100 milliseconds??
This is the definition of debounce that I have to implement:
Debounce ignores the calls made to it during the timer is running and
when the timer expires it calls the function with the last function
call arguments, so I want to achieve that with Javascript
A function will be debounced as follows:
receivedEvent = debounce(receivedEvent, 100)
My attempt:
function debounce(func, timeInterval) {
return (args) => {
setTimeout(func, timeInterval)
}
}
function receivedEvent() {
console.log('receive')
}
receivedEvent();
receivedEvent();
receivedEvent();
But this still generates 3 outputs. I need it to only produce one output according to the requirements.

In your attempt you did not call debounce, but just called your own function receivedEvent. Maybe the site where your attempt is tested will do this for you, but we cannot know this from your question. Just make sure it is called.
To test the requirements you need to use a better use case: one based on a function that receives arguments. This is needed because you must prove that the debounced function is called after the timeout with the last passed arguments.
The key to this pattern is to use variables within a closure:
function debounce(func, timeInterval) {
let timer;
let lastArgs;
return (...args) => {
lastArgs = args; // update so we remember last used args
if (timer) return; // not ready yet to call function...
timer = setTimeout(() => {
func(...lastArgs);
timer = 0; // reset timer (to allow more calls...)
}, timeInterval);
}
}
function receivedEvent(arg) {
console.log('receive ' + arg)
}
receivedEvent = debounce(receivedEvent, 100)
receivedEvent("a");
receivedEvent("b");
receivedEvent("c");
// Output will be "c" after 100ms
Note that the question's definition of "debounce" deviates a bit from its usual definition, where the first invocation actually calls the function immediately, and only then starts the timeout (cooldown-period).

Related

Javascript Debounce not clearing it's queue from vue component

I am using the debounce method from here https://www.freecodecamp.org/news/javascript-debounce-example/
function debounce(func, timeout = 300){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
function saveInput(){
console.log('Saving data');
}
const processChange = debounce(() => saveInput());
and I want to include in a library we have, so in common.js I have:
export default {
otherStuff,
debounce(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
};
},
in vue.js I have a textbox which has an event #keyup="searchboxChange()"
and in the methods section:
import common from "#/assets/js/Service/Common";
... stuff removed for brevity
methods:
{
searchboxChange(){
common.debounce(() => this.filterChanged(), 1000)();
},
}
I had to include () at the end of the debounce method else it didn't actually fire. However, while it debounces perfectly, when the timeout expires every event is then fired. So if my search was "HELLO" I would see 5 requests all fired at the same time as this.filterChanged() was called 5 times.
I am sure it is something simple with the scope of the timeout variable, because adding a console.log into the debounce method shows the timer is undefined each time.
You need to debounce the component method, otherwise you'll be invoking multiple debounced functions from within your component method.
Something like this should work
methods: {
// ...
searchboxChange: common.debounce(function() {
this.filterChanged();
}, 1000)
}
Notice the use of function as opposed to short function syntax. You'll need to do this to ensure the correct lexical scope of this
Firstly, as always, thanks to everyone who contributed a solution. However, none got past the "this" is not the right scope.
The solution was to set the function in created. (source: https://forum.vuejs.org/t/lodash-debounce-not-working-when-placed-inside-a-method/86334/4)
which (in case link goes dead) is effectively saying
move the part that you need to debounce into its own method and debounce that (like you did in the codepen for he first method).
Then you can call the debounced method from the event handler method.
It’s also better to debounce each instance method dynamically during created, otherwise component instances that the same debounced function and that can lead to wonky debounce behaviour:
and their code sample:
created() {
this.updateForm = _.debounce(this.updateForm, 500)
},
methods: {
triggerUpdate(event){
// perform some action before debouncing
this.updateForm(event)
} ,
updateForm: (event){
console.log('in update')
}
so for my case:
created() {
this.searchboxChange = common.debounce(this.filterChanged, 1000)
},
yes, that is literally it.
result:
only one network call now.

How to send data with delay vue js?

every time when i use input , my function send data to server and i get response, but if i want to write in field 'name' - Thomas Edison , i will send letter by letter
i try to put setTimeout function and if user still writing a string nothing will be send , but i does not work
#input="throttledSave"
throttledSave (e) {
let eva = e
let DELAY = 2000;
if(e.target.value){
return this.throttle(this.setDataFinalJSON, DELAY, eva);
}
},
throttle: function (callback, limit,eva) {
var wait = false;
var typingTimer;
return function (callback, limit,eva) {
clearTimeout(typingTimer)
if (!wait) {
callback(eva);
wait = true;
typingTimer = setTimeout(function () {
console.log('oh again')
wait = false;
}, limit);
}
}
}
every time it is work until DELAY , i don't know why, maybe clearTimeout does not work , i got stuck. I don't know why if i write some text so fast i got console.log('oh again')
You could do this with lodash debounce (https://lodash.com/docs/4.17.15#debounce) method:
Creates a debounced function that delays invoking func until after
wait milliseconds have elapsed since the last time the debounced
function was invoked. The debounced function comes with a cancel
method to cancel delayed func invocations and a flush method to
immediately invoke them. Provide options to indicate whether func
should be invoked on the leading and/or trailing edge of the wait
timeout. The func is invoked with the last arguments provided to the
debounced function. Subsequent calls to the debounced function return
the result of the last func invocation.
_.debounce(func, [wait=0], [options={}])
Example:
methods: {
throttledMethod: _.debounce(() => {
console.log('I only get fired once every two seconds, max!')
}, 2000)
}
Best to use the vue variant of lodash: https://www.npmjs.com/package/vue-lodash
Timeout just delays each input event (so that each one causes the request, just after some time) which is not what you want. The basic idea of implementing this is simple: store the time of the last input event in the model, and on input, send your requests only when timeout has passed, something like:
data () {
return {
...
lastInputTime: null,
inputTimeout: 1000 // ms
}
},
...
methods: {
throttledSave (e) {
const attemptTime = new Date();
if(this.lastInputTime && attemptTime - this.lastInputTime > this.inputTimeout) {
// get value, send request etc
}
this.lastInputTime = attemptTime;
}
Well, this is exactly what is called debounce, dreijntjens suggests a similar thing but using a library which allows to decorate your function.
PS Actually, such decorating is a better approach (unless you are planning to change inputTimeout in runtime) since you don't clutter your model with extra stuff specific to debouncing; you can make your own "decorator" (not in the strict sence, decorators are supposed to have special syntax, rather than being a function that gets your function and returns a modified one) if your project doesn't tree-shake libraries properly. Something like this:
function debounce(func, timeout) {
let lastTime = null;
return function() {
const attemptTime = new Date();
if(lastTime && attemptTime - lastTime > timeout) {
func.apply(this, arguments);
}
lastTime = attemptTime;
}
}
lodash's implementation is much more sophisticated since it supports several options.
How about using the lazy input model modifier?
VueJS prototype for delayed (lazy) input
Vue.prototype.lazyInput = function(e, delay) {
const self = this;
if (typeof delay == 'undefined') {
delay = 500;
}
const target = e.target;
if (self.lazyTimer) {
clearTimeout(self.lazyTimer);
self.lazyTimer = null;
}
self.lazyTimer = setTimeout(function(){
target.dispatchEvent(new Event('change'));
}, delay);
}
Usage:
<input v-model.lazy="{variableName}" #input="lazyInput($event)">
You can always use the native setTimeout()
methods: {
search: function (event) {
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
... XMLHttpRequest ...
}, 2000)
every 2000 msec sending request if no new data.

How to send discord messages after waiting 1 second

if (msg.content.toLowerCase() === "!start") {
var gov = setInterval(go, 1000);
var onev = setInterval(one, 1000);
var twov = setInterval(two, 1000);
function two(msg) {
msg.channel.send("https://i.imgur.com/JZOCg5l.png ");
}
function one(msg) {
msg.channel.send("https://i.imgur.com/gTK3Vhn.png ");
}
function go(msg) {
msg.channel.send("https://i.imgur.com/3iVfYIR.png ");
}
function two(msg) { }
function one(msg) { }
function go(msg) { }
msg.channel.sendFile("https://i.imgur.com/kOoyoZQ.png ").then(onev).then(twov).then(gov);
}
This is a very annoying task. I need to send these images about one second appart.
The current framework keeps giving me the following error:
C:\Users\maver\Documents\TestBot\testlev.js:197
msg.channel.sendFile("https://i.imgur.com/3iVfYIR.png ");
^
TypeError: Cannot read property 'channel' of undefined at Timeout.three [as _onTimeout]
(C:\Users\maver\Documents\TestBot\testlev.js:197:17)
at ontimeout (timers.js:478:11)
at tryOnTimeout (timers.js:302:5)
at Timer.listOnTimeout (timers.js:262:5)
I've tried this a multitude of different ways and am just about ready to throw in the towel.
Your syntax is slightly off. When you do function two(msg){... you are actually telling the function that you are going to pass it a new variable and that you want that variable called msg. Because of that, msg (in the context of your function) is undefined. You would have to pass in msg when you call the function from setInterval().
There are 2 ways you can bind msg to your function. The way that I personally like is this:
//...
var gov = setInterval(go.bind(null, msg), 1000);
var onev = setInterval(one.bind(null, msg), 1000);
var twov = setInterval(two.bind(null, msg), 1000);
//...
The .bind() function assigns the value of arguments. With the first argument of the function being called being the second argument of bind(). The first argument of bind() is what should be used as the value of this inside the function.
The other way to do this is with an anonymous function
//...
var gov = setInterval(function(){go(msg)}, 1000);
var onev = setInterval(function(){one(msg)}, 1000);
var twov = setInterval(function(){two(msg)}, 1000);
//...
Also note, setInterval() repeats a function call ever period. You may be looking for setTimeout() which would only fire the functions once after a delay.
When you use setInterval, you should know it will call the function, but will not provide any parameters to it (or even this). One way to fix it would be by using bind:
setInterval(go.bind(null, msg), 1000)
This would work, because bind() will create a new function where the parameters are "magically set in advance".
Another option in this case would be to simply not re-declare msg in the three functions - in that case, javascript will try to find msg from the outer scope, where it exists:
function two() {
msg.channel.send("https://i.imgur.com/JZOCg5l.png ");
}
Third, you shouldn't be using setInterval, but setTimeout, which will only call the function once.
The fourth problem you have is with timing. First, all three setTimeout calls happen at the same time, so all three functions will be called in one second (after 1000 millis). An easy fix would be simply:
setTimeout(go, 1000);
setTimeout(one, 2000);
setTimeout(two, 3000);
However, that will completely ignore how long it takes to send each message (which may or may not be what you want). If you wanted to wait a second after the previous message is sent, then you'd have to do something like:
msg.channel.sendFile("https://i.imgur.com/kOoyoZQ.png ").then(function() {
setTimeout(go, 1000);
});
function go() {
msg.channel.send("https://i.imgur.com/3iVfYIR.png").then(function() {
setTimeout(one, 1000);
});
}
// etc
That would be very tedious, as all the functions will look very similar. So a better approach would be to create a list of messages, then have a single function to send all of them:
var msgs = [
"https://i.imgur.com/kOoyoZQ.png",
"https://i.imgur.com/JZOCg5l.png",
"https://i.imgur.com/gTK3Vhn.png",
"https://i.imgur.com/3iVfYIR.png"
];
function sendMsgs(msgs, delay) {
if (msgs.length < 1) return; // we're done
var remain = msgs.slice(1);
var sendRemain = sendMsgs.bind(null, remain, delay);
msg.channel.send(msgs[0]).then(function() {
setTimeout(sendRemain, delay);
});
}
sendMsgs(msgs, 1000);
Your code is executed immediately because you have to maintain the value anf promises you are using is not correctly used.
You can do it as follows :
if (msg.content.toLowerCase() === "!start") {
var urls = ["https://i.imgur.com/kOoyoZQ.png",
"https://i.imgur.com/JZOCg5l.png",
"https://i.imgur.com/gTK3Vhn.png",
"https://i.imgur.com/3iVfYIR.png" ];
function gov(urls){
for(let k=0; k<urls.length;k++){
setTimeout(function() { msg.channel.send(k); },k*1000)
}
}
gov(urls);
}

JavaScript setInterval function may or may not have parameters

I am working on a simple higher-order function, delay, that will invoke a function parameter, func, and then delay by an amount of time passed in as wait. I have read other answers about where to put in parameters, but none of the answers I found addressed exactly what I need to learn: where and how should I allow for the parameters that may or may not be passed to func?
Original instructions: "Invokes func after wait milliseconds. Any additional arguments are provided to func when it is invoked."
Here's the basic start:
function delay(func, wait) {
setInterval(func, wait);
}
Another answer on SO states that an anonymous function can be used to wrap the func parameter so that the parameters can be passed in there, but I haven't had any success building that yet.
Guidance is greatly appreciated.
It sounds like you need to make use of the arguments pseudo-array, and Function#apply:
function delay(func, wait) {
// get all arguments after the first two
var args = Array.prototype.slice.call(arguments, 2);
setTimeout(function() {
func.apply(null, args);
}, wait);
}
Example:
function delay(func, wait) {
var args = Array.prototype.slice.call(arguments, 2);
setTimeout(function() {
func.apply(null, args);
}, wait);
}
function outputPerson(firstName, lastName) {
console.log("Hello, " + firstName + " " + lastName);
}
delay(outputPerson, 3000, "John", "Doe");
Edit: As Patrick Evans and I point out in the comments setTimeout already provides the functionality being described here (as long as you're not using IE <10). So you could even define your delay function like this:
var delay = setTimeout;
I think the correct way to model it is to acknowledge delays are only side effects and should not have parameters. The delay should be just the delay and that's it.
You might also want to use promises which are the new(ish) standard language way to perform signular asynchronous actions:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
See this question and answer about what the new Promise part does and what a promise API looks like.
Then, you can use the delay separately from the function call:
delay(1000).then(fn);
delay(1000).then(() => fn(arg1, arg2, arg3));
And so on.
If you want to keep using callbacks
setTimeout actually already does what you're asking for, setTimeout(fn, 1000) is the easy way to invoke a timeout but you can actually pass additional parameters to the function after the delay amount and the function will be called with them.
May be something link this:
function delay(func, wait,funcArguments) {
setInterval(function() {
funcArguments = funcArguments || [];
func.call(null, funcArguments);
}, wait);
}
funcArguments is array with arguments.
Usually in libraries they name it debounce and you can simply implement it like:
function debounce(func, wait=0,args=[]) {
setTimeout(function() {
func.call({}, args);
}, wait);
}
But a correct implementation, as lodash's has here, is much more complex.
You can just add the parameter for setInterval
var intervalID = window.setInterval(func, delay[, param1, param2, ...]);
function delay(func, wait, param) {
setInterval(func, wait, param);
}
function hello(x) {
var p = document.createElement('p');
p.innerHTML = 'Hello ' + x + '!';
document.body.appendChild(p);
}
delay(hello, 1000, 'world');
This is simple solution:
function delay(func, wait, ...param) {
setTimeout(function(){func(param);}, wait);
}
function doit(a){console.log(a);}
delay(doit,500,'test1','test2');
//['test1','test2']

Javascript: How do I tweak my debounce function to take an IF conditional?

I found a bug, and tracked it down.
You can see a simplified example of my code here.
As it turns out, I need to debounce my if() statement rather than debouncing the function itself.
I'd like to keep the debounce as a standalone function, but I'm not sure then how to pass the conditional in.
Any pointers?
Here's the code:
var foo = function(xyz) {
alert(xyz);
};
function setter(func, arg1, arg2) {
return {
fn: func,
arg1: arg1,
arg2: arg2
};
}
function debounce(someObject) {
var duration = someObject.arg2 || 100;
var timer;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function() {
someObject.fn(someObject.arg1);
timer = 0;
}, duration);
}
var toggle = true;
if (toggle) {
debounce(setter(foo, 'The best things in life are worth waiting for.', 1250));
} else {
foo('Instant gratification is sweet!!');
}
Using your example, why not pass toggle in as arg 1... something like:
var toggle = true;
var debouncedFunk = function(toggle) {
if (toggle)
// the function call
else
// something else
};
debounce(debouncedFunk, toggle, 1250);
You should also look into using the Function objects .call and .apply methods. They are for calling the function and passing in arguments. Taking the example function:
var example = function(one, two) {
// Logic here
};
You can call it in three ways:
// First
example(1, 2);
// Second
example.call({}, 1, 2);
// Third
example.apply({}, [ 1, 2 ]);
The first is the standard way to call a function. The difference between the first and the .call is that the first parameter to .call is the context object of the function (what this will point to inside the function), the other parameters are passed after that (and a known list is required for .call. The benefit of .apply is that you can pass an array to the function of arguments and they will be assigned to the parameter list appropriately, the first parameter is still the context object.
It would simplify your debounce function, instead of having to deal with a structured object as you currently do.
A suggestion for your debounce:
var debounce = function(funk, delay) {
var args = [];
if (arguments.length > 2)
args = [].slice.call(arguments, 2);
setTimeout(function() { funk.apply({}, args); }, delay);
};
Changing your current if to:
var toggle = true;
var debouncedFunk = function(toggle) {
if (toggle)
// Do if true
else
// DO if false
};
debounce(debouncedFunk, 1000, toggle);
Maybe too much information (sorry)?
As a last note, I'd recommend using a framework (if possible) where these functions have been implemented already (and many other useful functions) such as Underscore. Using Underscore your example would look like:
// Define debouncedFunk and toggle
debouncedFunk = _.bind(debouncedFunk, {}, toggle);
debouncedFunk = _.debounce(debouncedFunk, 1000);
debouncedFunk();
EDIT
Fixed the underscore example, _.debounce returns a function that will execute only after the delay but it still needs to be called.

Categories

Resources