Efficient way to request back-end for data on input change - javascript

This is more of a suggestion asking kind of question! I have a searchbox that user can type stuff in and the get a list of suggestion to choose from. I am using react, axios for data fetching and redux-saga for state managing.It is basically look like this:
handleChange(stringValue){
this.setState({
inputValue : stringValue
});
callServer(stringValue);
}
Now, everything works fine but the problem is that sending all those requests and handling the incoming response and changing state seems unnecessary because user doesn't stop to look at the suggestions in every char he types. I am looking for a way to only ask for suggestions when i know user is done fast typing. What i am thinking of doing looks like this :
handleChange(stringValue){
clearTimeOut(this.callerTimer);
this.callerTimer = null;
this.callerTimer = setTimeOut(function(){
this.callServer(stringValue);
this.callerTimer = null;
}.bind(this),300)
//i consider 300ms as the average time it takes people to stop typing and think
}
This works but i don't have a good feeling about it. So do you guys know any other clean and less timerly way to do what i want? is there any way to handle this in my saga effect or maybe an inbuilt time threshold thing in inputs that i am not aware of?

You want debounce functionality.
Basically it limits the rate at which a function can fire. So it waits a few ms before firing the event kind of like the user stopping the writing process.
Check this snippet
// 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);
};
};
// This will apply the debounce effect on the keyup event
// And it only fires 500ms or half a second after the user stopped typing
$('#testInput').on('keyup', debounce(function () {
alert('typing occurred');
$('.content').text($(this).val());
}, 500));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" id="testInput" />
<p class="content"></p>
Check this codesandbox for React solution
https://codesandbox.io/embed/green-meadow-16r3p?fontsize=14
Basically now it's up to you. Set your own time in ms and you're good to go. There is no need to install any additional dependencies to your project.
Lodash has a debounce function but you don't want to install all of lodash just for one function.

I highly recommend using debounce from lodash: https://lodash.com/docs/4.17.11#debounce
From the docs:
Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked.
Therefore you pass your request function to debounce, so you limit the number of requests to your server.

Related

understanding the debounce code from geeksforgeeks

Can someone help me understand how this code snippet is going to work?
In geeks from greeks they have following code snippet
<html>
<body>
<button id="debounce">
Debounce
</button>
<script>
var button = document.getElementById("debounce");
const debounce = (func, delay) => {
let debounceTimer
return function() {
const context = this
const args = arguments
clearTimeout(debounceTimer)
debounceTimer
= setTimeout(() => func.apply(context, args), delay)
}
}
button.addEventListener('click', debounce(function() {
alert("Hello\nNo matter how many times you" +
"click the debounce button, I get " +
"executed once every 3 seconds!!")
}, 3000));
</script>
</body>
</html>
From the following Difference Between throttling and debouncing a function, it says, emit only if there is no pending action (and after specific second) Answer
Here I can't see any if condition to check if there is a pending action.
From the article they say
> If the debounce button is clicked only once, the debounce function
> gets called after the delay. However, if the debounce button is
> clicked once, and again clicked prior to the end of the delay, the
> initial delay is cleared and a fresh delay timer is started. The
> clearTimeout function is being used to achieve it.
So everytime the button is clicked, the timer is cleared and a new timer is started (and probably the new action is also triggered), which probably doesn't sound anything like what I read from SFO answer
Also, Can someone please also explain how func.apply(context, args), delay) is used or code-line by line?
Here I can't see any if condition to check if there is a pending action.
There is no explicit checking done instead the 3 second is the max window that will be used to decide anything.
From the quoted answer at https://stackoverflow.com/a/57524083
Debounce (1 sec): Hi, I am that ^^ robot's cousin. As long as you keep pinging me, I am going to remain silent because I like to reply only after 1 second is passed since last time you pinged me. I don't know, if it is because I have an attitude problem or because I just don't like to interrupt people. In other words, if you keeping asking me for replies before 1 second is elapsed since your last invocation, you will never get a reply. Yeah yeah...go ahead! call me rude.
So if there was a click action and 3s has elapsed you run the event handler function, in short it is sort of "piggybacking". Pools in all your demands then gives it a go at once when it is sure that you won't be asking anymore
Regarding the 2nd part func.apply(context, args), delay) - this is used because of the nature of scoping in javascript. In your example they haven't used anything related to the concept of this keyword but just add this line this.innerText = 'hello' after the alert, you would expect the text of button to change to "hello" and indeed it would work. But now replace func.apply(context, args) with func(args) and it wouldn't work...
Why? Because of the nature of this keyword, it is inferred based on the place where the function is called from and NOT from the place where it is in inside the source code
So when after 3s setTimeout calls this function () => func( args), this points to setinterval and you know this.innerText = 'hello' doesn't make any sense on setInterval object(Yes everything is an object in javascript even functions too!)
What apply does is it allows you to manipulate the this context that will be "resolved" inside a given function upon its invocation; doesn't matter where your desired function lies whether inside a class, an object or simply in global scope, as long as it's reachable you can pass it your arguments and an object that it will assume as its this
function fullName(greeting) {
alert(greeting + ', ' + this.firstName + " " + this.lastName);
}
var person1 = {
firstName: "John",
lastName: "Doe"
}
var person2 = {
firstName: "Santiago",
lastName: "Adan"
}
fullName.call(person1, "hello");
fullName.call(person2, "hola");

How to trigger a call when typing is stopped

<input autocomplete="off" [config]="config2" [items]="items2" (inputChangedEvent)="onInputChangedEvent($event)" (selectEvent)="onSelect($event)">`enter code here`
onInputChangedEvent(val: string) {
this.changeEvent.emit(val);
this.inputChanged = val;
if (this.timer) {
clearTimeout(this.timer);
}
// trigger the search action after 400 millis
this.timer = setTimeout(this.searchFunction(val), 200);
}
I am using InputChangedEvent ,how can we delay the event
You can't pass a function with arguments to setTimeout(), you need to create another function where in you call this function:
var _this = this;
setTimeout(function () {
_this.searchFunction(val);
}, 200);
By passing the function directly to setTimeout, JavaScript executes the function and uses the return value as the callback. So your searchFunction is executed every time.
Are you asking how to trigger the action only if there has been no typing for 400msec?
If so, the usual approach is a "deadman's switch" (name borrowed from an old safety device on trains, I believe).
The basic idea is that each time you see a keystroke, you kill the existing timer (if any) and start a timer for 400msec.
Then, if the timer finally triggers, you know that 400msec have passed without a keystroke, and you can do the action.

How to provide a dynamic (calculated) css property

I am working on task in which each time a user scrolls or resizes the screen I would like to recalculate a css properties for an element.
Let's say I want to impl. a progress bar and the progress is reported based on the scroll position in the window
<div class='progress-bar-wrap'>
<div class='progress-bar-progress'></div>
</div>
function updateScrollProgress () {
this.progressIndicator.css('width', this.calculateProgressBarWidth() + 'px');
}
I tried to hook on scroll and resize events, but this seem to have a laggy effect.
window.on('scroll', updateScrollProgress)
window.on('resize', updateScrollProgress)
I tried in the scroll and resize event handlers to requestAnimationFrame
window.on('scroll', function(){window.requestAnimationFrame( updateScrollProgress))
window.on('resize', function(){window.requestAnimationFrame( updateScrollProgress))
And experienced huge improvement in most browsers, however it is yet occasionally laggy.
I tried to request another frame, when from the requestAnimationFrame handler:
function updateScrollProgress () {
window.requestAnimationFrame( updateScrollProgress)
this.progressIndicator.css('width', this.calculateProgressBarWidth() + 'px');
}
This completely eliminated the laggy effect, but comes to the cost of endless loop of calls to this method, even when no recalculations are needed.
Is there a way to hook a handler just before the browser decides to draw element(s), so that I can provide/set the those "dynamic" css values for a property?
What's what you're doing when you use requestAnimationFrame. If you've gotten rid of the lag using it, it's unclear why you say the function is running "too often." Usually in this sort of context, "too often" means "is causing lag" (by running too often and slowing things down). If yours isn't, then...?
If you want the handler called less often, you can debounce it, but then you'll probably notice delays before the changes you want (because you've debounced), which it sounds like is what you're trying to avoid.
In any case, requestAnimationFrame is, for now at least, the right tool for the "just before the browser renders the frame" job.
Wrap your event function through a debounce function:
More info on debounce functions here:
https://davidwalsh.name/javascript-debounce-function
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);
};
};
var myEfficientFn = debounce(function() {
// All the taxing stuff you do
}, 250);
window.addEventListener('resize', myEfficientFn);

Detect low processing speed

I have a web app that is essentially a text box that the user can type in.
The user types in the box and then clicks a Submit button, and it executes a script to display an image.
Using AngularJS I can also have the same JS function called whenever the user types in the box, removing the need to click the button and offering a much smoother experience.
However, this means that this rather lengthy and intensive function can be called multiple times per second, especially for fast typers. On a desktop, this is no problem. On a mobile - at least, an entry-level mobile - it's extremely slow and is a horrible experience.
The automatic submission behaviour is controlled by a boolean variable that is TRUE by default.
On mobile, I would like to set this variable to FALSE. Even better would just be to set it to false for slow devices, but I don't think that's possible to detect. What's the easiest way of doing this?
You can use a debounce function to only run the function after X time to stop typing, In this article you can find how to implement it
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);
};
};
But if you still want to detect if it is a mobile device, you can use this small function
function isMobile() {
return /Mobi/.test(navigator.userAgent) || /Android/.test(navigator.userAgent);
}
Some of these answers might help you out:
https://stackoverflow.com/search?q=determine+mobile
(but for fun...)
Welll... wonky way first.. you could do a performance timer when loading the page.
show a busy indicator..
create a counter x++ for 1 second..
if x > 10000.. fast device (total out of my butt number)
if x < 10000.. slow device
remove busy indicator and proceed
Like I said.. that's wonky and people will argue a) you're wasting time and b) you're wasting battery. I would not make that argument if it really improves the user experience later.
There's also the library many folks use to determine the browser 'type' and the features it provides.

Javascript Poll server. Will this cause a stack overflow?

I am not too familiar with the specifics of every javascript implementation on each browser. I do know however that using setTimeout, the method passed in gets called on a separate thread. So would using a setTimeout recursively inside of a method cause its stack to grow indefinitely until it causes a Stack Overflow? Or would it create a separate callstack and destroy the current frame once it goes out of focus? Here is the code that I'm wondering about.
function pollServer()
{
$.getJSON("poll.php", {}, function(data){
window.setTimeout(pollServer, 1000);
});
}
window.setTimeout(pollServer, 0);
I want to poll the server every second or so, but do not want to waste CPU cycles with a 'blocking loop' - also I do not want to set a timelimit on how long a user can access a page either before their browser dies.
EDIT
Using firebug, I set a few breakpoints and by viewing the "Script -> Stack" panel saw that the call stack is literally just "pollServer" and it doesn't grow per call. This is good - however, do any other implementations of JS act differently?
I am not sure if it would create a stack overflow, but I suggest you use setInterval if the period is constant.
This is how prototype implements its PeriodicalExecuter.
// Taken from Prototype (www.prototypejs.org)
var PeriodicalExecuter = Class.create({
initialize: function(callback, frequency) {
this.callback = callback;
this.frequency = frequency;
this.currentlyExecuting = false;
this.registerCallback();
},
registerCallback: function() {
this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},
execute: function() {
this.callback(this);
},
stop: function() {
if (!this.timer) return;
clearInterval(this.timer);
this.timer = null;
},
onTimerEvent: function() {
if (!this.currentlyExecuting) {
try {
this.currentlyExecuting = true;
this.execute();
} finally {
this.currentlyExecuting = false;
}
}
}
});
setTimeout executes sometime later in the future in the event pump loop. Functions passed to setTimeout are not continuations.
If you stop and think about it, what useful purpose or evidencec is there that the call stack is shared by the timeout function.
If they were shared what stack would be shared from the setter to the timeout function ?
Given the setter can do a few returns and pop some frames - what would be passed ?
Does the timeout function block the original thread ?
Does the statement after the setTimeout function execute after the timeout executes ?
Once you answer those questions it clearly becomes evident the answerr is NO.
setTimeout does not grow the callstack, because it returns immediately. As for whether your code will run indefinitely in any browser, I'm not sure, but it seems likely.
take a look at the jQuery "SmartUpdater" plugin.
http://plugins.jquery.com/project/smartupdater
Following features are available:
stop() - to stop updating.
restart() - to start updating after pause with resetting time interval to minTimeout.
continue() - to start updating after pause without resetting time interval.
status attribute - shows current status ( running | stopping | undefined )
updates only if new data is different from the old one.
multiplies time interval each time when data is not changed.
handle ajax failures by stopping to request data after "maxFailedRequests".

Categories

Resources