js debounce logic understanding - javascript

Wanted to see if experts here help explain how to understand the below debounce logic better. I got this from an Udemy course but the video only got so much explanation. From the code below.. this is what I understand it does - everytime an "input" is detected setTimeout inside debounce function will execute and after 1 second it'll remove the timeout.
How does the spread operator and the "args" come into play in this?
I understand the spread operator takes in an array and then "spreads" it out as separate arguments into the function parameter. Am I misunderstanding how the func.apply works here? How does the return(...args) read the input value arguments?
It seems like "onInput" is func and the return values from "onInput" is passed as arguments to ...args?
const debounce = func => {
let timeoutID;
return (...args) => {
if(timeoutID) clearTimeout(timeoutID);
timeoutID = setTimeout(
() => func.apply(null, args),
1000
);
};
}
const onInput = event => {
fetchData(event.target.value);
};
input.addEventListener("input", debounce(onInput));

A few things to clarify:
debounce returns a (anonymous arrow) function. It doesn't do much more than that, except that it provides a closure for the variables func (the argument passed to debounce) and timeoutID. So when you call debounce(onInput) the returned function will know about onInput.
The above-mentioned returned function becomes the handler for the input event. So when that event triggers that anonymous arrow function is called with the event object as argument. So args will be [event object]. This anonymous function will clear any pending timer event and will schedule a new one that will call func.apply(null, args).
When the timer expires without being cleared, then func.apply(null, args) will be called. We know what func is: onInput and we know what args is: [event object]. So that means we effectively call onInput with the event object as argument.
As to your questions:
How does the spread operator come into play in this?
It is the rest syntax, and allows to capture all the arguments into one array.
How does the "args" come into play in this?
As explained above, as the event handler is called with one argument, args will be initialised to an array with the same arguments that the event handler would get, i.e. with the event object.
Am I misunderstanding how the func.apply works here?
All you wrote about it is that the function will execute. apply is a method that calls the function with a specific value for this (which is not the goal here, so just null is provided), and with an array that should be used as arguments in the call. As args is exactly that array of arguments we got, and we just want to pass them on, that is what is passed here as well.
It should be said that in modern JavaScript you'd achieve the same result with func(...args);
How does the return(...args) read the input value arguments?
First of all that is not the complete statement. The complete statement includes the whole arrow function, which is the object that is returned here. Like explained above, debounce returns a function (object).
This code "only" returns that function, it does not read input value arguments. It provides the function that will later do this. Realise that debounce is called now, while that returned function is only called when the event fires.
Rewritten...
It may clarify things when rewriting the code in a slightly different way. I hope it better highlights the difference between returning a function and executing it. This can happen at different times.
function debounce(func) {
let timeoutID;
// Define a local function. Don't execute yet.
// It takes any arguments, as it will just remember them
// for passing them to the original function later on (after timer).
function debouncedFunc(...args) {
// If we had a pending timer job, clear it, as we want a new one.
if (timeoutID !== undefined) {
clearTimeout(timeoutID);
}
// Create a function to execute later. It should itself
// execute func with the arguments we already know about.
function executeFunc() {
// This is the more modern syntax to do func.apply(null, args):
// It is easier to read; we just call func with some known arguments
func(...args);
}
// Schedule that function to run later, and get a handle for that job
timeoutID = setTimeout(executeFunc, 1000);
}
// Return the new function object to the caller.
// They can call it with some arguments, or let
// some other API call it for them (like with addEventListener)
return debouncedFunc;
}
function onInput(event) {
fetchData(event.target.value);
}
// Get a function object
const debouncedInputHandler = debounce(onInput);
// Make that function the event handler
input.addEventListener("input", debouncedInputHandler);

Related

how does the returned function of JavaScript debounce function get all parameters through arguments

generally for js debounce function, a simple implement goes like
function debounce(func, wait) {
let timerId = null;
return function (...args) {
console.log('args',args);
clearTimeout(timerId);
timerId = setTimeout(() => func.apply(this, args), wait);
}
}
and when you use it, you can do things like
var random = function() {console.log(arguments)};
var test = debounce(random, 1000);
test(1,2,3);
my question is, how does the returned function inside debounce function can get all attributes that gets passed into test function (here being 1,2,3) through arguments object? I feel like it might have to do with closure, but can anyone explain?
I've also created a jsFiddle for simpler view
https://jsfiddle.net/u4n07veb/22/
Another question would be in this js fiddle, my console.log args can print 1,2,3, since 1,2,3 is what I pass to test function, but would it also be possible to get 4,5,6 inside the debounce function? Since 4,5,6 is the parameters I pass to the callback function of the debounce function?
Though the arguments special object does exist in JavaScript, in the implementation of debounce above the function arguments are instead retrieved via a rest parameter.
The difference is minimal - arguments is not a true Array but instead an Array-like object, whereas the ...args rest parameter method will retrieve an actual Array - but it's one worth mentioning.
The actual passing through of these arguments happens when Function.prototype.apply is called, which allows a function to be called with a given value of this and a specified array of arguments. It's a sibling to the similar Function.prototype.call, which takes each argument to passed through as a separate parameter.
So in your example, you call test(1, 2, 3) and that executes the function that was returned by debounce(random, 1000). That function gets its arguments as an Array via the ...args rest parameter, and then passes that array through to the random function via func.apply(this, args).
To answer your question about passing a different set of parameters through, I recommend you try it and see. But the answer is yes: with this setup the debounced function is able to pass through any number of arguments.
Closures aren't directly relevant to how the arguments are passed through here. They are relevant in a different way, however, in that the timerId variable created when debounce is called is kept in a closure so that later attempts to call the debounced function will access it again, which is what allows the innards of this debounce implementation to clear the timeout it had created during its previous execution.

Can someone explain me the flow of functions in this example?

I looked at the above piece of code and tried my best to search for solutions and posted it here after giving it my all. This is my current understanding of the code:
debounce() is called when there is an input and onInput() is passed to it as a callback, and debounce function return another function , the function being returned takes an argument which is the function passed by the debounce() a.k.a the onInput() , I am stuck # func.apply(null , args);
1.Isn't func and args the same thing ????
Someone please explain step by step is possible..
debouce is only called once on the initial run, it creates and returns a new anonymous function - the actual event handler.
When the input event is triggered, the previously created function is executed and will call func (onInput) after 500ms. func is only once passed to debounce, but args are the actual input event arguments, which will be passed on to func via apply. In this case, apply is basically the same as func(...args); So func (aka onInput) will be called with the actual arguments from the input event after 500ms.
This is an example of debounce.
Debouncing is a practice which is used to improve browser performance.
A programming practice which ensure that time-consuming tasks do not fire so often.
It is used to limits the rate at which a function gets invoked.
I have explained debounce with example, please check out the link
debounce

Event listeners with arguments

I want to pass arguments to an event listener in Javascript.
I have found solutions however I cannot understand why or how they work and why other solutions do not work.
I have a C/C++ background, however in Javascript functions perform a lot different.
Could you please help me understand how and why the following examples work or not work?
Without parameters the code works fine:
var clicker = document.getElementById("mainNavToggle");
clicker.addEventListener("click", eventFunction);
function eventFunction()
{
console.log("works");
}
If I include brackets on the eventListener the function executes once without the event firing and then does nothing:
var clicker = document.getElementById("mainNavToggle");
clicker.addEventListener("click", eventFunction());
function eventFunction()
{
console.log("works");
}
I suppose this is because the function is invoked at the eventListener and this doesn't allow the eventListener function to call the eventFunction later.
When I want to pass an argument with the eventFunction.
The following does not work:
var clicker = document.getElementById("mainNavToggle");
clicker.addEventListener("click", eventFunction("works"));
function eventFunction(myStr)
{
console.log(myStr);
}
It invokes the eventFunction without the event firing and then when the events fires does nothing which is the same behaviour as previously.
So far I think I can understand.
To pass the argument correctly in the event function I found the following code.
The following solution works fine:
var clicker = document.getElementById("mainNavToggle");
clicker.addEventListener("click", eventFunction("works"));
function eventFunction(myStr)
{
function func(event, myStr)
{
console.log(myStr);
}
return function(event) {
func(event,myStr)
};
}
The first thing I notice is that this eventFunction is not invoked immediately.
Why is that?
However, I am at a loss of how arguments are passed within each function.
I understand that the "works" literal is passed to myStr on the eventFunction.
However in the function func there is an additional event parameter not passed by eventFunction.
Also why does the eventFunction needs to return another function with one parameter that internally calls the func function?
Also please notice that I want to pass parameters to the eventFunction that do not have a global scope, otherwise there would not be a need to pass parameters.
Let's dissect this step-by-step.
General information
The .addEventListener function/method takes in 2 (or 3) arguments:
a String, with a value of the event type/name (for example "click")
a pointer to a Function, which should be executed when the event occurs
a Boolean, specifying if event bubbling or event propagation should be used. This argument is optional and can be omitted.
Example 1
In you first example, you pass a String ("click") and a pointer to a Function (eventFunction) into .addEventListener. Everything works as expected, the event occurs and the function is executed.
Example 2
In this example, you pass a String and undefined to .addEventListener, which is not what .addEventListener expects. You might ask yourself "When did I pass in undefined?". Well, it happened as soon as you executed your eventFunction by adding parens () after it. Here, the syntax in JS is equal to the syntax in most other languages, adding parens to the right side of a function executes the function.
Because your eventFunction doesn't return anything explicitly, it automatically returns undefined. Therefor, this is what .addEventListener got:
clicker.addEventListener("click", undefined)
However, because you executed eventFunction, it logs "It worked" to the console once.
Example 3
This example fails for the same reasons example 2 failed.
Example 4
The code in the last example works, because it uses a concept called closure. There are literally thousands of explanations of the concept, so my explanation here will be really short. If you have trouble understanding it, just google for
"javascript closures".
In contrast to a lot of other languages, JavaScript has function scoping instead of block scoping. So if you define a variable inside a function F and return another function G from within F, G will have access to variables and arguments from inside F. For demonstration purposes:
function F () {
var fVariable = 'Defined in F';
return function G () {
return fVariable; // <-- this is the closure
}
}
var funcG = F(); // <-- execute F
funcG(); // -> 'Defined in F';
Compare this short example with your fourth code: They are pretty much the same. The only difference is, that your code creates an extra function func.
Now, if you call clicker.addEventListener("click", eventFunction("it works")),
you execute eventFunction, which returns another (anonymous/lambda) function which encapsulates the "it works" string via a closure. If we write it out by hand, this is what .addEventListener "sees":
clicker.addEventListener("click", function (event) {
func("it works", event);
});
EDIT: Solving the problem
Here's a working example, note that I changed the function names to reflect their purpose:
function eventHandlerFactory (myStr) { // <-- eventFunction in your code
return function executeOnEvent (event) { // <-- executed if an event occurs
console.log(myStr);
console.log(event);
}
}
clicker.addEventListener("click", eventHandlerFactory("it worked"));
The eventHandlerFactory function returns a function executeOnEvent, which is passed into .addEventListener. Writing it out by hand again, this is what the interpreter understands:
clicker.addEventListener("click", function executeOnEvent (event) {
console.log("it works");
console.log(event);
});
The first thing i notice is that this eventFunction is not invoked
immediately
It is invoked immediately. However, it returns a new function. This is the function that is registered for the click handler. This function, when called, calls the function func in a closure. The arguments passed to this function are also preserved in a closure.
To understand this concept, you need to understand how closures work. This is a topic that has been written about extensively, so I'll just point you to How do JavaScript closures work?.

Javascript: setTimeout beahavior in this example [duplicate]

Simply put...
why does
setTimeout('playNote('+currentaudio.id+', '+noteTime+')', delay);
work perfectly, calling the function after the the specified delay, but
setTimeout(playNote(currentaudio.id,noteTime), delay);
calls the function playNote all at the same time?
(these setTimeout()s are in a for loop)
or, if my explanation is too hard to read, what is the difference between the two functions?
The first form that you list works, since it will evaluate a string at the end of delay. Using eval() is generally not a good idea, so you should avoid this.
The second method doesn't work, since you immediately execute a function object with the function call operator (). What ends up happening is that playNote is executed immediately if you use the form playNote(...), so nothing will happen at the end of the delay.
Instead, you have to pass an anonymous function to setTimeout, so the correct form is:
setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);
Note that you are passing setTimeout an entire function expression, so it will hold on to the anonymous function and only execute it at the end of the delay.
You can also pass setTimeout a reference, since a reference isn't executed immediately, but then you can't pass arguments:
setTimeout(playNote, delay);
Note:
For repeated events you can use setInterval() and you can set setInterval() to a variable and use the variable to stop the interval with clearInterval().
You say you use setTimeout() in a for loop. In many situations, it is better to use setTimeout() in a recursive function. This is because in a for loop, the variables used in the setTimeout() will not be the variables as they were when setTimeout() began, but the variables as they are after the delay when the function is fired.
Just use a recursive function to sidestep this entire problem.
Using recursion to deal with variable delay times:
// Set original delay
var delay = 500;
// Call the function for the first time, to begin the recursion.
playNote(xxx, yyy);
// The recursive function
function playNote(theId, theTime)
{
// Do whatever has to be done
// ...
// Have the function call itself again after a delay, if necessary
// you can modify the arguments that you use here. As an
// example I add 20 to theTime each time. You can also modify
// the delay. I add 1/2 a second to the delay each time as an example.
// You can use a condition to continue or stop the recursion
delay += 500;
if (condition)
{ setTimeout(function() { playNote(theID, theTime + 20) }, delay); }
}
Try this.
setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);
Don't use string-timeouts. It's effective an eval, which is a Bad Thing. It works because it's converting currentaudio.id and noteTime to the string representations of themselves and hiding it in the code. This only works as long as those values have toString()s that generate JavaScript literal syntax that will recreate the value, which is true for Number but not for much else.
setTimeout(playNote(currentaudio.id, noteTime), delay);
that's a function call. playNote is called immediately and the returned result of the function (probably undefined) is passed to setTimeout(), not what you want.
As other answers mention, you can use an inline function expression with a closure to reference currentaudio and noteTime:
setTimeout(function() {
playNote(currentaudio.id, noteTime);
}, delay);
However, if you're in a loop and currentaudio or noteTime is different each time around the loop, you've got the Closure Loop Problem: the same variable will be referenced in every timeout, so when they're called you'll get the same value each time, the value that was left in the variable when the loop finished earlier.
You can work around this with another closure, taking a copy of the variable's value for each iteration of the loop:
setTimeout(function() {
return function(currentaudio, noteTime) {
playNote(currentaudio.id, noteTime);
};
}(currentaudio, noteTime), delay);
but this is getting a bit ugly now. Better is Function#bind, which will partially-apply a function for you:
setTimeout(playNote.bind(window, currentaudio.id, noteTime), delay);
(window is for setting the value of this inside the function, which is a feature of bind() you don't need here.)
However this is an ECMAScript Fifth Edition feature which not all browsers support yet. So if you want to use it you have to first hack in support, eg.:
// Make ECMA262-5 Function#bind work on older browsers
//
if (!('bind' in Function.prototype)) {
Function.prototype.bind= function(owner) {
var that= this;
if (arguments.length<=1) {
return function() {
return that.apply(owner, arguments);
};
} else {
var args= Array.prototype.slice.call(arguments, 1);
return function() {
return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
};
}
};
}
I literally created an account on this site to comment on Peter Ajtai's answer (currently highest voted), only to discover that you require 50 rep (whatever that is) to comment, so I'll do it as an answer since it's probably worth pointing out a couple things.
In his answer, he states the following:
You can also pass setTimeout a reference, since a reference isn't executed immediately, but then you can't pass arguments:
setTimeout(playNote, delay);
This isn't true. After giving setTimeout a function reference and delay amount, any additional arguments are parsed as arguments for the referenced function. The below would be better than wrapping a function call in a function.
setTimeout(playNote, delay, currentaudio.id, noteTime)
Always consult the docs.
That said, as Peter points out, a recursive function would be a good idea if you want to vary the delay between each playNote(), or consider using setInterval() if you want there to be the same delay between each playNote().
Also worth noting that if you want to parse the i of your for loop into a setTimeout(), you need to wrap it in a function, as detailed here.
It may help to understand when javascript executes code, and when it waits to execute something:
let foo2 = function foo(bar=baz()){ console.log(bar); return bar()}
The first thing javascript executes is the function constructor, and creates a function object. You can use either the function keyword syntax or the => syntax, and you get similar (but not identical) results.
The function just created is then assigned to the variable foo2
At this point nothing else has been run: no other functions called (neither baz nor bar, no values looked up, etc. However, the syntax has been checked inside the function.
If you were to pass foo or foo2 to setTimeout then after the timeout, it would call the function, the same as if you did foo(). (notice that no args are passed to foo. This is because setTimeout doesn't by default pass arguments, although it can, but those arguments get evaluated before the timeout expires, not when it expires.)
After foo is called, default arguments are evaluated. Since we called foo without passing arguments, the default for bar is evaluated. (This would not have happened if we passed an argument)
While evaluating the default argument for bar, first javascript looks for a variable named baz. If it finds one, it then tries to call it as a function. If that works, it saves the return value to bar.
Now the main body of the function is evaluated:
Javascript looks up the variable bar and then calls console.log with the result. This does not call bar. However, if it was instead called as bar(), then bar would run first, and then the return value of bar() would be passed to console.log instead. Notice that javascript gets the values of the arguments to a function it is calling before it calls the function, and even before it looks up the function to see if it exists and is indeed a function.
Javascript again looks up bar, and then it tries to call it as a function. If that works, the value is returned as the result of foo()
So, function bodies and default arguments are not called immediately, but everything else is. Similarly, if you do a function call (i.e. ()), then that function is executed immediately as well. However, you aren't required to call a function. Leaving off the parentheses will allow you to pass that function around and call it later. The downside of that, though, is that you can't specify the arguments you want the function to be called with. Also, javascript does everything inside the function parentheses before it calls the function or looks up the variable the function is stored in.
Because the second one you're telling it to call the playNote function first and then pass the return value from it to setTimeout.

Why can't the generators next function be a callback function for an event listener [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 7 years ago.
I was experimenting with generators for a bit. When I tried this piece of code I got unexpected errors:
Uncaught TypeError: Method [Generator].prototype.next called on incompatible receiver #
or
TypeError: CallStarGeneratorMethodIfWrapped method called on incompatible HTMLButtonElement
My question, does it not work ? What is the meaning behind those error messages ? And most importantly; Why is first().next not handled as a normal function ? Why does the addEventListener care about the origins of the first().next function. Type first().next in the console. It says function. Below is out commented a similar function to the next except it produces always the same result.
The code, that you can try to reproduce:
<html>
<button id="test">Click</button>
<script>
var first = function* (){
console.log("first click");
yield true;
console.log("second click");
yield true;
};
document.getElementById("test").addEventListener('click', first().next);
/*var wouldwork = function (){
console.log("would work");
return { value: true, done: false };
// same as the next returns
};*/
/*document.getElementById("test").addEventListener('click', wouldwork);*/
</script>
</html>
Another option would be to put next with the correct context in another function. To do that we store the iterator in a variable.
var iterator = first();
document.getElementById("test").addEventListener('click',
function (event){return iterator.next();}
//context of next is always the correct one
);
If that happens more often it can be a good idea to create a new function named createNext that returns a next function in a more pure functional style
var createNext = function (generatorFunction){
var iterator = generatorFunction();
return function() {
return iterator.next();
};
};
document.getElementById("test").addEventListener('click',
createNext(first)
);
jsFiddle Demo
The way that event listen works is that it will call the function handle using call and assign the this binding from its execution context to the called function handle. So this is going to be bound as the context for next being called. That is why it doesn't work as written.
Next, there is the issue of actually getting the function from your generator function for iteration. "Calling a generator function does not execute its body immediately; an iterator object for the function is returned instead.". This means that yes, first().next is a function object, but it isn't the handle you want to pass. You simply want to use the iterator function itself, but that would be first(), so how does that work if you want it to call next each time?
An option here is simply to take your generator function and pass the iterator function in later as a binding. In order to maintain the iterator function's binding inside of .next, you could do this:
document.getElementById("test").addEventListener('click', first().next.bind(first()));
This will bind the iterator function for first() to the iterator function's .next function. Kind of messy. The first call to first() exposes the iterator function and then accesses its next function, the second call simply binds the next function's this to the iterator function which is otherwise overwritten when the eventListener overrides the this binding using call.
You can read more in general about these generator functions and their iterators here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*

Categories

Resources