I want to debounce same function in two diferent scenarios, when user clicks search button and when user stops typing. So if the user types cat and in less than 1 second he clicks 3 times search icon we will only search one time, after 1 second he stops clicking search button.
I tried this:
function debounce(your_func,time=1000){...}
function search_api(){...}
$("#my_input").on("input",
debounce(function(){search_api()})
);
$("#search_button").on("click",
debounce(function(){search_api()})
);
This works but not exacly what we want cause it debouce it "separately", so it debounces inputs by on hand and clicks to search on the other hand.
This is because you trigger the same function so it will work as you expected.
You might think as the debounce function have a private var: timer(returned by setTimeout).
Each time you trigger this function it reset the old timer, and send a new SetTimeout, here is a simple version of debounce function.
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());
If you separate two function , which means you create two new debounced function , and they both have a private timer and setTimeout function
I found the solution but don't know why is working despite reading a lot of documentation about functions and callbacks:
function debounce(your_func,time=1000){...}
function search_api(){...}
const debounce_search = debbounce(search_api)
$("#my_input").on("input",
debounce_search
);
$("#search_button").on("click",
debounce_search
);
Related
How I need to:
First keyup on (search input) fires up search countdown timer and set timer to 1s.
Every next search keyup on (search input) reset search countdown timer to 1s.
When (search input) no longer updated and stay the same -
Search countdown timer reached 0s and performing a search.
Why I need to (this way):
I have large database of records and using ajax method to fetch through. Every time keyup on (search input) send a request... (it's resource waste and uncomfortable to watch how page refresh every keyup). So my way is to create "typing phase" - what I mean describe above.
Only pure JS, no libraries! Thanks.
var timer=null;
document.getElementById("myInput").onkeyup = ()=>{
clearInterval(timer);
timer = setInterval(()=>{
console.log("call ajax search function here.");
clearInterval(timer);
},1000);
};
<input type="text" id="myInput">
what you need is called debounce function.
if you don't use libraries like lodash - you can take this realisation:
function debounce(f, ms) {
let isCooldown = false;
return function() {
if (isCooldown) return;
f.apply(this, arguments);
isCooldown = true;
setTimeout(() => isCooldown = false, ms);
};
}
then you can code like this:
const handleSearch = (search) => {
// your logic with fetch or whatever
}
const handleSearchDebounced = debounce(handleSearch, 300)
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.
I'm kinda piggybacking off a question I asked earlier. But in the following piece of code, I want to be able to click this button multiple times but then fire the function only once. I tried using a setTimeout but the function will still fire multiple times after the delay. Next, I've tried debounce from lodash, but this is just behaving exactly the same what as the setTimeout Have I missed something in my code?
const handler = debounce(() => {
myFunction(name, age)
}, 1000);
function update() {
handler();
}
<StyledQuantityButton disabled={ amount <= 0 }
onClick={() => {
const newValue = amount - 1;
setAmount(newValue);
update();
}} >
—
</StyledQuantityButton>
While you might have put the myfunction(name, age) part in debounce, your setAmount part is still outside the debounce.
Depending on how your component re-renders, the debounced function might also get recreated (therefore losing its "I just got called" status), which would lead to the same problem. Try this:
const handler = React.useMemo(() => debounce(() => {
setAmount(amount => amount - 1);
myFunction(name, age);
}), [myFunction]);
// ...
onClick={handler}
im trying to do something similar to google docs. It's an autosave system/loop. I'll save a document after a few seconds of inactivity (by calling a save method from the class).
If there is some activity during that few seconds, then I will reset/prolong the timer by another few seconds. This cycle will continue until the save method is being called, of which i will break the loop and wait for another activity to start the loop again.
Here's some pseudo code i came up with:
doc.on('mouse:up', (event) => {
... //<--- Initialize the timer on mouse:up event
... //<--- When time is up, execute the save method
})
doc.on('mouse:down', (event) => {
... //<--- prolong timer initialized in the mouse:up event if it is available.
}
However, i don't think this way of doing is possible as I cant access the same initialized setTimeOut object. Is there a good way to implement this? would love to hear from yall.
There are a few ways to handle this.
Approach: Basic
Cons: Low level, behavior is not as immediately obvious.
const saveDelayMs = 1000;
let timeoutId;
doc.on('mouse:up', (event) => {
timeoutId = setTimeout(save, saveDelayMs);
});
doc.on('mouse:down', (event) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(save, saveDelayMs);
}
Approach: Debounce Abstraction
Pros: Higher level, code more readable
// Trigger 1000ms after last call
const debouncedSave = debounce(save, 1000);
doc.on('mouse:up', (event) => {
debouncedSave();
});
doc.on('mouse:down', (event) => {
debouncedSave();
});
function debounce(fn, debounceDelayMs) {
let timeoutId;
return () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(fn, debounceDelayMs);
};
});
Lodash provides a debounce function, which I recommend over creating your own.
Simply initialize the variable outside of both functions. This way, it will be accessible inside either of them :
let theTimeout = null;
doc.on('mouse:up', (event) => {
theTimeout = setTimeout(...); //<--- Initialize the timer on mouse:up event
})
doc.on('mouse:down', (event) => {
clearTimeout(theTimeout);
theTimeout = setTimeout(...); //<--- prolong timer initialized in the mouse:up event if it is available.
}
You could use a debouncing method (see here: https://davidwalsh.name/javascript-debounce-function), but probably the clearest if you are going to look at multiple types of inputs (say, dragging an object stops saving, but also touch, and also pointers etc..), it's best to use a global flag and use it to toggle the save flag from anywhere in your code, so anybody can trigger the suggestion for saving:
let saveFlag = false;
setInterval(function(){
if( saveFlag === true ){
console.log( 'save' );
saveFlag = false;
} else {
console.log( 'skip save' );
}
}, 3000);
// Now you can trigger a save on any kind of event,
// and expect it to happen within 3 seconds.
document.addEventListener( 'click', e => saveFlag = true);
document.addEventListener( 'touchend', e => saveFlag = true);
document.addEventListener( 'pointerup', e => saveFlag = true);
This will also prevents duplicate timers from potentially being created, and has a maximum delay or 3000ms in my example, with very little overhead.
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).