How to implement debounce in function call in javascript - javascript

I asked a question How to fire Ajax request after user inactivity in 1.5 seconds on how to update user data after user has finished typing instead on every key up, the I was referred to a post where they recommended using javascript debounce, which I love How to implement debounce fn into jQuery keyup event? but I can't seem to find a way to implement it into my code, this is what the solution I was referred to said I should do
$('input').keyup(debounce(function(){
var $this=$(this);
//alert( $this.val() );
var n1 = $this.val();
var n2 = $('#n2').val();
var n3 = $('#n3').val();
var calc = n1 * n2 * n3;
alert(calc);
},500));
my function works perfectly but I dont want it running every time, so I don't know how to implement debounce in this case, this is the code that I want to add debounce to
//cart.php
<input type="text" class="count_cart content_to_cart oninput qty-remove-defaults" onkeyup="setcart(this)" onkeydown="autoSave(this)" id="updates_486" >
function autoSave(e){ //JS where I want to implement debounce
setTimeout(function(){
var getsizes_response = $(e).attr("id");
var getsave_response_quantity = $("#"+getsizes_response).val();
$.ajax({
type: "POST",
url: "functions.php",
data: {getsizes_response: getsizes_response, getsave_response_quantity: getsave_response_quantity},
cache: false,
timeout: 5000,
success: function(r){
$("#ajax_responses_get_positioner").show();
$("#ajax_responses_get_positioner").html(r);
},
error: function(){
$("#ajax_responses_get_positioner").show();
$("#ajax_responses_get").html("Error saving order, please reload page and try again.");
$("#ajax_responses_get").css({'background': 'red', 'display': 'block'});
$("#ajax_responses_get_positioner").delay(6000).fadeOut("fast");
}
});
}, 1500);
}

Debounce takes a function as an argument, and returns a function (the debounced version of the original function). Functions that do this are called "higher order functions". In the first example, the function isn't named, and it is passed directly to debounce as soon as it is created. In the second example, you've named the function autosave (which is a great thing to do), but it doesn't make it any harder. All you have to do is call debounce before using your function name. So when you use it on keyup:
$('input').keyup(debounce(autoSave, 500))
If you wanted to always debounce autosave, you could save the debounced function to the variable autoSave, so after that point, it is always debounced. (Or you could use a different name - up to you).
autoSave = debounce(autoSave, 500)
$('input').keyup(autoSave)
Re-assigning a decorated function to the original function name is called "decorating", and the higher order function is called the decorator. Some languages provide a special syntax for this, and javascript is considering adding one as well:
#debounce(500)
function autoSave() { … }
That would do the same as the previous example: call debounce with autoSave and then assign it to autoSave. However, if you want to do that today, you need to use babel or typescript and the right plugins.

After help from garrett motzner, I figured it out.
<input type="text" class="count_cart autow content_to_cart oninput qty-remove-defaults" onkeyup="setcart(this)" id="updates_486" >
$('.autow').keydown(debounce(function(){
var qthis=$(this);
var getsizes_response = $(qthis).attr("id");
var getsave_response_quantity = $("#"+getsizes_response).val();
$.ajax({
type: "POST",
url: "functions.php",
data: {getsizes_response: getsizes_response, getsave_response_quantity: getsave_response_quantity},
cache: false,
timeout: 5000,
success: function(r){
$("#ajax_responses_get_positioner").show();
$("#ajax_responses_get_positioner").html(r);
},
error: function(){
$("#ajax_responses_get_positioner").show();
$("#ajax_responses_get").html("Error saving order, please reload page and try again.");
$("#ajax_responses_get").css({'background': 'red', 'display': 'block'});
$("#ajax_responses_get_positioner").delay(6000).fadeOut("fast");
}
});
},500));
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
This allows it to call only once, after user finish typing.

Related

How to save value to chrome storage without button?

Need to save input's value of type text to chrome.storage.sync.set without uses ok button.
<input id="titleUserTab" type="text">
<input id="urlUserTab" type="text">
Problem
Event change of text element triggers by lost it focus. In this case, problem is need to button ok in order to lost focus.
Event input of text element triggers by each keyboard's button. Problem is limits of chrome.storage.sync:
MAX_WRITE_OPERATIONS_PER_HOUR = 1800 The maximum number of set, remove, or clear operations that can be
performed each hour.
MAX_WRITE_OPERATIONS_PER_MINUTE = 120 The maximum number of set, remove, or clear operations that can be
performed each minute.
Description
My solution
In order to remove button ok, use the input event.
And to save the values, I added an interval and deleted the handler before the trigger time.
What do you think about this case? Maybe you have your own solution? Thanks for any help.
// onload page
function restoreUserTabContext(data) {
titleUserTab.value = data.title;
urlUserTab.value = data.url;
titleUserTab.addEventListener('input', onInputUserTabContext);
urlUserTab.addEventListener('input', onInputUserTabContext);
}
// handler
function onInputUserTabContext() {
titleUserTab.removeEventListener('input', onInputUserTabContext);
urlUserTab.removeEventListener('input', onInputUserTabContext);
setTimeout(function () {
let data = {
title: titleUserTab.value,
url: urlUserTab.value,
};
titleUserTab.addEventListener('input', onInputUserTabContext);
urlUserTab.addEventListener('input', onInputUserTabContext);
saveOption('dataUserTab', data);
}, 4000);
}
function saveOption(key, value) {
option = {};
option[key] = value;
chrome.storage.sync.set(option, function () {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
}
});
}
Instead of updating data for every character, we can update change on input blur event. Which will be triggered only after the input is blurred.
So this only updates when the input is blurred (like when we click outside the input)
function onBlur() {
let data = {
title: titleUserTab.value,
url: urlUserTab.value,
};
saveOption('dataUserTab',data);
}
titleUserTab.addEventListener('blur', onBlur);
urlUserTab.addEventListener('blur', onBlur);
But if you want the data to be updated dynamically before the blur, it's better to use debounce. So when after every change we call save function with a delay, If a new change comes before the delay is over, the delay gets cancelled and it waits for the new delay to be completed. Which is something similar to your approach, but simpler as we don't need to clear the event listeners and add them frequently. The debounce function will be like this
function debounce(func, wait, immediate) {
var timeout;
return function executedFunction() {
var context = this;
var args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
function onBlur() {
let data = {
title: titleUserTab.value,
url: urlUserTab.value,
};
saveOption('dataUserTab', data);
}
titleUserTab.addEventListener('input', debounce(onBlur, 1000));
urlUserTab.addEventListener('input', debounce(onBlur, 1000));
The above code waits for 1 second before updating the data.
ref
We also can use lodash library for debounce
You could potentially promisify chrome.storage.sync.set then only create a new call the the storage API once it resolves.
The idea sounds good, but you could instead use "setInterval" along with a "previous value" variable:
var oldData = null;
setInterval(
()=>{
var data = {
title: titleUserTab.value,
url: urlUserTab.value,
};
if (data!=oldData) {
saveOption('dataUserTab', data);
}
oldData=data
},
4000);

What is the right way to reset a timeout?

so I have a function like this:
function blabla(){
...
setTimeout(() => {
//do some stuff
}, 10000)
}
now How can I reset the time of the timeout (10000) if function was called and timeout was not finished yet?
I tried to kill the timeout if it does exist like this:
function blabla(){
...
if(to){
clearTimeout(to)
}
let to = setTimeout(() => {
//do some stuff
}, 10000)
}
but I get error that to is undefined. so what is the right way to check if a timeout exists or not. is there a better way to do this?
You just need declare to before the if, so that it exists when the if runs (and there is not undefined). You don't have to give it an actual value until later.
Realistically, you probably want to declare it outside the function, so it will persist next time you call the function.
Here's a runnable demo. Notice that despite calling blablah() twice, you only see "hello" once, because the second call to the function cancelled the original timeout.
var to;
function blabla() {
//...
if (to) {
clearTimeout(to)
}
to = setTimeout(() => {
//do some stuff
console.log("hello");
}, 10000)
}
blabla();
blabla();
dont use let, let scope is inside the function block.
if you call the function the second time, the function does not have let to defined.
use var so it is accessible within across function call.
Not good idea use global var for that, because it is not reusable.
Better write wrapper for that function, because it is common pattern. This native code or use npm packet for that
Debounce functions are included in many JavaScript libraries. The goal
behind each implementation is to reduce overhead by preventing a
function from being called several times in succession. Regardless of
the library, all debounce functions are built on JavaScript's native
setTimeout function.
https://www.npmjs.com/package/debounce:
function debounce(func, wait, immediate) {
let timeout;
return function() {
let context = this,
args = arguments;
let later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
let callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
var blabla = debounce(function(){
console.log(5)
}, 5000);
blabla()
blabla()

JS - Set delay between sending AJAX request

I am working on building a search feature into my site. To do this I have a search bar which sends an AJAX request to my server each time a user types something. The server will in turn send back the items which match the search.
The only problem with this currently is that if a user types "a" and then "b" what it will send is:
a
ab
To counter this I have found setTimeout which delays when the user enters a search; however, currently it is only delaying when the strings fire (i.e. waits 0.75 seconds before sending a and then waits 0.75 seconds before sending ab).
Here's the JS:
$('#searchBox').keyup(function(e) {
var timeoutID = setTimeout(searchRequest, 750, $(e.currentTarget).val());
});
function searchRequest(str) {
if (str.length > 0) {
console.log('search request initalized | sending: ', str);
var xhttp = new XMLHttpRequest();
xhttp.open('POST', 'code to send here', true);
xhttp.send(str);
}
}
I think what you need is a debounce function.
Here's the basic JavaScript debounce function (as taken from Underscore.js):
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
You can use it to debounce click:
$('#searchBox').keyup(debounce(function(e) {
searchRequest$(e.currentTarget).val());
}, 750));
You have to clear thew timeout to make it work. Have a look on this link ;) Resetting a setTimeout
Basically, when the user added a letter, you check if you already defined a timeout. If you defined a timeout you clear it. Then you reset the timeout. Something like that
var yourTimeout;
function sendInfo(info){
if (yourTimeout != undefined)
clearTimeout(yourTimeout);
yourTimeout = setTimeout(function(){
//do something
}, 500);
}
In your case, the sendInfo function is the keyup handler, and you call searchRequest in the timeout like you already did ;)
Hope it helps

JavaScript: window.setTimeout(), force early expire

I have a <div> on my page that refreshes automatically every two minutes with updated log entries. When I first load my webpage, I call the following function.
function getLogs() {
var filter = $('#filter').val();
$.get("index-ajax.asp", { queryType: "getLogs", filter: filter,
uTime: new Date().getTime() },
function(data){
$("#logEntries").html(data);
window.setTimeout("getLogs()",120000);
});
}
I know the above code could be cleaner with window.setInterval(...); but I just like the control of window.setTimeout(...);.
My question, is it possible to cancel the next timeout execution? In the event that I change the filter, I'd like to cancel the next timeout, and call the function right away, which would reschedule the timeout function. Is there a better way to achieve that result?
Note that the above code is in jQuery.
Yes, use clearTimeout.
Ex:
var clr = window.setTimeout(getLogs,120000);
The when you wan to clear it:
clearTimeout(clr);
setTimeout returns a timerID that you can pass to clearTimeout:
// Note we are passing the *function* rather than a string
// Also note the lack of () - we are *not* calling the function
// setTimeout will do that for us
var timerID = setTimeout(getLogs, 120000);
// Fake condition - we cancel the timer if the timerID is even
if (timerID % 2 === 0) {
clearTimeout(timerID);
}
You could always define a new variable based on a filter value and if that filter value is set, use a while statement to omit the timeout:
if(filter == "whatevs"){
var i=true;
}
function(data){
$("#logEntries").html(data);
while(i!=true){
window.setTimeout("getLogs()",120000);
}
}

Passing variables between functions

I have two functions, one that makes an Ajax request when the user loads the page, and one that will run every 5 or so seconds to update something. Using the first function, I can output a variable that I need to use in the second function.
function insert_last_ten() {
$.ajax({
url: 'freeshout/chatlog.php',
success: function(data) {
$("#inner-wrap").html(data);
var first_child = $("#inner-wrap :first-child").html();
var value = first_child.match(/(value)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?/);
var realtime = value[2];
}
});
}
Basically, I need to use realtime to do something else in another function. For the sake of simplicity, let's pretend this is the second function:
function update() {
alert(realtime);
}
How could I go about making that work?
In the success callback, cancel the timeout and start a new one using the updated value. You can pass the timeout identifier to insert_last_ten via argument and the success callback will pick it up via closure:
function createUpdateTimer(value, interval) {
return setTimout(
function () {
alert(value); // The created function knows what value is due to closure
}, interval);
}
function insert_last_ten(timer) {
$.ajax({
url: 'freeshout/chatlog.php',
success: function(data) {
$("#inner-wrap").html(data);
var first_child = $("#inner-wrap :first-child").html();
var value = first_child.match(/(value)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?/);
var realtime = value[2];
cancelTimer(timer); // This callbac knows what timer is due to closure
timer = createUpdateTimer(realtime, 500);
}
});
}
// Start the timer:
var timer = createUpdateTimer('initial value', 500);
// Make ajax request:
insert_last_ten(timer);
Note that I am only beginning to familiarize myself with the good parts of JavaScript. This code is untested.

Categories

Resources