JS - Set delay between sending AJAX request - javascript

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

Related

Mix Javascript debounce (not plugin) with jquery event handler?

Instead of including a debounce plugin library for a very small form section of my site, I've implemented some of David Walsh's JavaScript Debounce Function in my code to allow a small wait for slower user input, however I am binding it to the input field using jQuery instead of regular DOM event listener. The code works fine but my main question here is some peer input on if this is ok to do (will it cause unforeseen issues? is there a better method?) I couldn't find another example on this scenario on stack overflow but if it has been already asked, please do point me in the right direction.
$(function() {
// debounce function by David Walsh
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);
};
};
// allow .5s for slower user input before processing
var debounceCustomInput = debounce(function() {
// if both inputs valid
if ($("#left").val().length > 0 && $("#right").val().length > 0) {
// process form
myAjaxFunction();
}
}, 500);
// event binding
$(document).on("input", "#left,#right", function() {
// debounce input
debounceCustomInput();
});
});

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()

Function called on interval accesses global variable

I am asynchronously receiving data (from 0 to 100 points per second) that is passed to a high-startup-cost function, extendTraces(data), which updates the user interface. If I call extendTraces() upon generating every point, the user interface becomes unresponsive. I've found that it is much more efficient to call the function periodically and pass it an array of points, call it pointArray.
I can track how many points have been added to pointArray and call extendTraces(pointArray) every 20th addition:
//inside asynchronous function
pointArray.push(point);
if (this.pointArray.length == 20){
(<any>Plotly).extendTraces(this.pointArray);
this.resetPointArray();
}
But if I fill pointArray halfway and don't receive any data for a while, I'd also to call extendTraces.
My solution is to call extendTraces() every second
//inside a function that is called when the page loads
window.setInterval(function() {
if (pointArray.length > 0){
(<any>Plotly).extendTraces(this.pointArray);
this.resetPointArray();
}
}, 1000);
My function that receives the points will simply concatenate them onto the pointArray.
//inside asynchronous function
pointArray.push(point);
I am new to js and was wondering if I'm using the correct paradigms for this task. I see a lot of information about callbacks and promises which I don't fully understand but I am suspicious that I'm doing something wrong by not using them. Coming from c++, I am concerned that two functions, the function defined in setInterval and the asynchronous function that receives points, both have access to the pointArray without any hardcoded mutex.
I would be tempted to wrap the logic into its own class, where it simply allows you to specify
After how many adds to call the method
After how long of inactivity to call the method
the method to call
function PointHandler(flushSize, flushTime, flushCallback){
var pointArray = [];
var lastFlush = setTimeout(() => this.flush(),flushTime)
this.addPoint = function(point){
pointArray.push(point);
if(pointArray.length == flushSize){
this.flush();
}
clearTimeout(lastFlush)
lastFlush = setTimeout(() => this.flush(), flushTime);
}
this.flush = function(){
flushCallback(pointArray);
pointArray = [];
clearTimeout(lastFlush)
lastFlush = setTimeout(() => this.flush(), flushTime);
}
}
var handler = new PointHandler(10, 5000, points => console.log(points));
document.getElementById("clickme").addEventListener("click", () => handler.addPoint(new Date()));
<button id="clickme">Add point</button>
The above code will call the callback after 5 seconds of inactivity, or when it gets 10 points added.
My callback simply console.log the current points, but you could call your method.
You should create a debounce function, this basically limits how often the function can be called. Here is the debounce function from the underscore library:
// 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.
_.debounce = function(func, wait, immediate) {
var timeout, result;
var later = function(context, args) {
timeout = null;
if (args) result = func.apply(context, args);
};
var debounced = restArguments(function(args) {
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(later, wait);
if (callNow) result = func.apply(this, args);
} else {
timeout = _.delay(later, wait, this, args);
}
return result;
});
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};
Now just wrap your extendTraces function around the debounce function and then call the function that is return from it.
Source: https://github.com/jashkenas/underscore/blob/master/underscore.js#L887-L914

Efficient async filter

I have a computationally heavy section of code which performs filtering of a data-set by DOM-manipulations. I made it async in order to ensure acceptable performace for large number of fiter items:
.on('input', function {
setTimeout(function(){
// heavy computation
}, 0)
})
The problem is that this section probably run on every user input change but only the result of the last one is of interest. So, my question is if there is a way to cancel the previous "threads" executing the heavy computation section and only execute the latest?
The best idea I have thus far is to use some sort of semaphore (possibly $.Deferred) and try to reduce the critical section.
If you only need the final result, use debounced event handler. This is a sample debounce implementation, but there are other on the web.
Wrapping your event handler with debounce will prevent the event handler from firing, as long as it's called repeatedly. The handler will fire, only if the handler is called, and then it's idle for a set amount of time.
.on('input', debounce(function {
// heavy computation
}, 500)) // wait 500ms before firing the handler
In the example type continuously and stop, you'll see that the console will log the word called only once:
var debounce = function(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 || 200);
if ( callNow ) {
func.apply(context, args);
}
};
};
$('#input').on('input', debounce(function() {
console.log('called');
}));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="input">

Write a scroll callback that gets called at regular intervals

I want to write a scroll callback function that gets called after every one second when user scrolls. I tried
window.on('scroll', function(e) {
//scroll function body
});
But the problem is it gets called everytime user scrolls even one pixel. Can someone please suggest a solution.
You can write a function like this
scrollCb = function(func, later) {
var timeout;
return function() {
var context = this, args = arguments;
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
and call it like
scrollCb(function() {
//Your scroll callback handler
}, 1000);

Categories

Resources