I'm having problem understanding why the below debounce code does not work?
you can see the below code in the following: link
`
HTML:
<input type="text" onkeyup="betterFunction(event)"/>
JS:
let newValue;
let counter = 0;
const getData = () => {
// dummy call to API and get Data
console.log("Fetching Data ..", newValue,counter++);
}
const debounce = function (fn, d) {
let timer;
return function () {
clearTimeout(timer);
timer = setTimeout(() => {
fn();
}, d);
}
}
const betterFunction = ({target:{value}}) => {
newValue=value;
debounce(getData, 2000); // **line 1**. placing this line of code debouncing does not happen
intermediate() // **line 2**. replacing this line of code with the above line debouncing works
}
const intermediate = debounce(getData, 2000);
`
I understand that the debounce function returns another function which acts like a closure in JavaScript but why the above line 1 code does not work but the line 2 code works
debounce function returns a function which is never called when you call debounce as
debounce(getData, 2000);
dobounce function doesn't needs to return a function. You just need following steps to implement debounce function:
Check if timer is undefined or not. If not, that means there's a timeout that we need to cancel.
After that set a new timer by calling setTimeout() that calls the given function after specific amount of time.
Also, timer should not be a local variable because you don't want it to reset whenever debounce function is called.
let counter = 0;
let newValue;
let timer;
const getData = () => {
console.log("Fetching Data ..", newValue, counter++);
}
const debounce = function(fn, d) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(fn, d);
}
const betterFunction = (e) => {
newValue = e.target.value;
debounce(getData, 2000);
}
<input type="text" onkeyup="betterFunction(event)" />
If you don't want to declare timer as a global variable and want to return a function from debounce function, then you need to call the debounce function once initially and whenever keyup event fires on the input element, you call the function returned from the debounce function instead of calling the debounce function.
let counter = 0;
let newValue;
const getData = () => {
console.log('Fetching Data ..', newValue, counter++);
};
const debounce = function(fn, d) {
let timer;
return function() {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(fn, d);
};
};
const intermediate = debounce(getData, 2000);
const betterFunction = (e) => {
newValue = e.target.value;
intermediate();
};
<input type="text" onkeyup="betterFunction(event)" />
i hope that what you want :
let counter = 0;
// you need to define timer and newValue here first
let timer , newValue;
// defining input as varible for good usage better than usage in html
var input = document.querySelector("#inp");
const getData = () => {
// increment first better than in console :)
counter+=1;
console.log("Fetching Data .." , newValue , counter);
// as last step clear timer for next timeout
clearTimeout(timer);
}
// givin value direct to timer directlly worked better than return
const debounce = function (fn, d) {
timer = setTimeout(fn, d);
}
const betterFunction = () => {
// newvalue must equal input value
newValue = input.value;
// and then calling debounce as last step
debounce(getData, 2000);
}
// here giving onkeyup event to input for getting values and start working :)
input.onkeyup = betterFunction;
Related
I have react app & need to trigger a function once user stops typing in the textbox. This is what I tried.
const inputHandler =(e:SynthenticEvent) => {
let timer: any = null;
clearTimeout(timer);
timer = setTimeout(() => {
console.log('hi');
//in actual sending an Http request, however hiding it here for brevity
},1000);
}
<input type="text" onChange={inputHandler} name="userInput" />
What's happening as of now, that the inputHandler trigger after 1000ms but it trigger the multiples times (ex 100 is triggering 3 times, 1000 triggers 4 times)
Which is not ideal or expected?
I thought to make use of useEffect, but on doing so inputHandler() is not in identified/scope by textbox?
What I'm looking is at trigger the function after 1000ms but once with
latest value of the texbox?
Thanks!
This called debounce
Debounce function.
Javascript code.
function debounce(func, timeout = 300){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
}
}
Typescript code.
function debounce<Params extends any[]>(func: Function, timeout =
300) {
let timer: ReturnType<typeof setTimeout>;
return (...args: Params) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this as Function,
args); }, timeout);
}
}
To use it.
const handleChange = debounce(() => console.log('hi'))
<input type="text" onChange={inputHandler} name="userInput" />
Thanks to #Mina for letting me know about debouncing. On googling around debounce in javascript, I found a minor fix in my code which addressed my requirement precisely
I was almost close, all I was doing wrong was that I had defined & initialized the timer inside the function, just moving outside the target function resolved the issue
let timer: any = null; //moved outside the function
const inputHandler =(e:SynthenticEvent) => {
clearTimeout(timer);
timer = setTimeout(() => {
console.log('hi');
//in actual sending an Http request, however hiding it here for brevity
},1000);
}
I am new to React so as part of learning I am trying to do a js debounce function when typed in input box (a search box simulation). But for some reason it is not working. The function is getting called each for key up than once for 2000 ms or delay specified in timeout. Below is the link to sandbox code for your reference. I have referred across blogs, the implementations seems the same yet I could not figure out what is the issue.
https://codesandbox.io/s/react-debouncing-9g7tc?file=/src/search.component.js
Issue :
const debounce = (func, delay) => {
let t; // <-- this will be new variable each time function get called
return function() {
clearTimeout(t); // <--- this also points to the new one, so it will clear nothing
// so all the previously called func will be called
t = setTimeout(() => func(), delay);
};
};
1st Solution : useRef
const t = useRef(null);
const debounce = (func, delay) => {
return function() {
clearTimeout(t.current); // <--- pointing to prev setTimeout or null
t.current = setTimeout(() => func(), delay);
};
};
WORKING DEMO
2nd Solution : Define t outside scope of SearchUi
var t;
const SearchUi = () => {
...
const debounce = (func, delay) => {
return function() {
clearTimeout(t);
t = setTimeout(() => func(), delay);
};
};
...
};
WORKING DEMO
im new in javascript and i want to make random function to display random image for simple scissor rock paper game,
but i have problem when the callback cannot assign value in variable,
anyone have solution?
thanks before
this is my code
const arr_option = ['scissors', 'rock', 'paper', 'scissors', 'rock', 'paper'];
function random() {
const selector = document.getElementsByClassName('img-komputer')[0];
var i = 0;
var chooseComp = 'Initial Value';
const callback = () => {
var randIndex = Math.floor(Math.random() * arr_option.length);
var comp = arr_option[randIndex];
selector.src = 'img/' + comp + '.png';
if (++i === arr_option.length - 1) {
chooseComp = comp;
clearInterval(refInt);
}
};
const refInt = setInterval(callback, 250);
return chooseComp; // this is value not update
}
Well, as first you're returning compChoose when you updated chooseComp. Btw, js doesn't work in this way, when a value is returned from a method it will not change also if you change into the caller function.
function caller() {
let returnedValue = called();
// here returnedValue is not updated when it's myVar is updated in called function
}
function called() {
let myVar = 12;
setInterval(() => {
// here you are updating myVar but after a copy of this variable is already returned
myVar++;
}, 1000);
return myVar; // here a copy of myVar is created and returned
}
The best option is to use a callback:
function caller() {
// this function inside the parenthesis is the callback
called(function (updatedValue) {
console.log(updatedValue);
});
}
function called(callback) {
let myVar = 12;
callback(myVar);
setInterval(function() {
myVar++;
// create a copy of new value of myVar and use it to call the callback
callback(myVar);
}, 1000);
}
In this way the callback is called every time the myVar is updated and send the new value.
Please let me know if it's clear enough.
I can't see where you define compChoose.
I want to use a JS Throttle. But i'm struggeling to get it work correctly.
I tried the code from this article:
https://codeburst.io/throttling-and-debouncing-in-javascript-b01cad5c8edf
But the Throttle does not work as intended, since everytime i click on the button, one "|" is added to the div. No clicks were discarded.
where is the misstake?
function foo() {
$("#respond").append("|");
}
const throttle = (func, limit) => {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
var onClick = function() {
throttle(foo(), 50000);
};
$('#button').click(onClick);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="button" id="button" value="Click Me" />
<div id="respond"></div>
In order for throttle(func, limit) to work, there can only be one instance of its product.
The problem is that the onClick function in your example creates a new instance each time it is called.
This makes the underlying inThrottle variable meaningless, as a new copy is created for each click.
The solution is to call one single instance the product of throttle(foo, 50000) directly.
Also, foo itself should be passed (not its product).
See below for a practical example, as well as closures and scope for more info.
// Foo.
const foo = (...args) => {
$("#respond").append("|");
}
// Throttle.
const throttle = (func, limit) => {
let inThrottle
return (...args) => {
if (!inThrottle) {
func(...args)
inThrottle = setTimeout(() => inThrottle = false, limit)
}
}
}
// On Click.
const onClick = throttle(foo, 1000)
// Button - Click.
$('#button').click(onClick);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="button" id="button" value="Click Me" />
<div id="respond"></div>
I know the question is old.
I made a small snippet. I think it should qualify as a simple way to throttle a function. This will guarantee a call every wait milliseconds.
function throttle( fn, wait ){
let lastCall = 0;
return function(){
if( Date.now() - lastCall > wait ){
lastCall = Date.now()
fn()
}
}
}
var counter = 0
var count = () => document.querySelector("#count").textContent = ++counter
window.addEventListener('scroll', throttle( count, 1000 ) )
body{
height: 2000px;
}
<h1 id='count'>
0
</h1>
Your onClick is creating a new throttled function on every invoke. You have to ensure that is only throttled once
var onClick = function() {
throttle(foo(), 50000);
};
// TO
var onClick = throttle(foo, 50000);
I am trying to automatically pass data from an input field into a function if no new data has been entered after 1000 ms. However, its behavior is inconsistent and sometimes the function runs twice on a single input.
<paper-input id="itemId" on-input="automaticInput"></paper-input>
...
automaticInput() {
let timeout = null;
let that = this;
console.log(timeout); // logging each keystroke
input();
function input() {
console.log('input');
clearTimeout(timeout);
timeout = setTimeout(function() {
that.validateInput();
}, 1000);
}
}
validateInput() {
if (this.$.itemId.value) {
this.doSomething(); // runs twice
}
}
How do can I properly set the automaticInput function to only run once per input string?
What you need is a debounce function. Your code is mostly here, just move timeout var out of automaticInput:
let timeout = null;
automaticInput() {
clearTimeout(timeout);
let that = this;
timeout = setTimeout(function() {
that.validateInput();
}, 1000);
}
Or better create debounce fn like:
function debounce(fn, timeout = 0) {
let timeoutId = null;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), timeout);
}
}
and then you code become:
const debouncedValidateInput = debounce(this.validateInput.bind(this), 1000);
<paper-input id="itemId" on-input="debouncedValidateInput"></paper-input>
If you use something like lodash it already shipped with such helper.