Javascript : Scoping in Asynchronous Execution (using Callbacks) - javascript

I need some explanation to understand clearly what is happening here ;
We have these two code samples , the first one logs to the console -1 five times ,and that is because the for loop executes completely leaving i with the value –1, and only then do the callbacks start executing. The problem
is, when they execute, i already has the value –1.
The second sample logs the expected result which is a 5 to 1 countdown.The only difference between the two samples is that i is no longer declared in the scope of the countdown() function , but why does that change the execution and how is the value of i being handled in the second sample?
Code Sample #1
function countdown() {
let i; // note we declare let outside of the for loop
console.log("Countdown:");
for(i=5; i>=0; i--) {
setTimeout(function() {
console.log(i===0 ? "GO!" : i);
}, (5-i)*1000);
}
}
countdown();
Code Sample #2
function countdown() {
console.log("Countdown:");
for(let i=5; i>=0; i--) { // i is now block-scoped
setTimeout(function() {
console.log(i===0 ? "GO!" : i);
}, (5-i)*1000);
}
}
countdown();

In sample 1, the variable is declared just inside the function. You get a new i every time countdown() is called. Within countdown() the variable changes. By the time the timeout runs, i will be on its lowest value.
In sample 2, the variable is declared just inside the loop. You get a new i every time the loop goes around. This means you get a new i for every function you pass to setTimeout. The different timeouts are no longer sharing the same variable.

The key difference is the use of the let keyword within the for loop.
In code sample #1, i is declared outside of the for loop. That means each access to i in each closure causes the same i to be dereferenced, because i is within the identical block scope of each closure, which is why we get -1 each time.
In code sample #2, i is declared within the for loop, which means a new i is created for each execution. Each closure refers its own, exclusive block in the for loop which has a unique i. Thus, we get the expected 5 to 1 countdown.
For use of ES6 features like let, it can be handy to use the Babel REPL to determine how scoping rules are applied. The following simplified versions of code blocks #1 and #2:
for (var j = 0; j < 5; ++j) {
setTimeout(function() { console.log(j); }, 1);
}
for (let i = 0; i < 5; ++i) {
setTimeout(function() { console.log(i); }, 1);
}
...are compiled into:
"use strict";
for (var j = 0; j < 5; ++j) {
setTimeout(function () {
console.log(j);
}, 1);
}
var _loop = function _loop(i) {
setTimeout(function () {
console.log(i);
}, 1);
};
for (var i = 0; i < 5; ++i) {
_loop(i);
}
Notice how, for code block #2, i gets copied into a distinct function scope using the _loop function where each closure refers to its own i.
Check it out here.

Related

JavaScript Function inside the loop

Can someone explain to me why JSLint complains about "Function inside the loop" with this example:
for (var i = 0; i < buttons.length; i++) {
(function(i) {
buttons[i].onclick = function(e) {
t.progressBars[t.current].update(buttons[i].getAttribute("data-value"));
}
})(i);
}
But dosen't when I change it to:
function makeHandler(i)
{
return function() {
t.progressBars[t.current].update(buttons[i].getAttribute("data-value"));
}
}
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = makeHandler(i);
}
I don't quite understand as it seems that with each loop iteration new function object has to be returned, even though it happens inside of makeHandler() function. Why the second example is ok with JS linters?
Quoting from linterrors,
var elems = document.getElementsByClassName("myClass"), i;
for (i = 0; i < elems.length; i++) {
(function (iCopy) {
"use strict";
elems[i].addEventListener("click", function () {
this.innerHTML = iCopy;
});
}(i));
}
What we have now captures the value of i at each iteration of the loop. This happens because JavaScript passes arguments to functions by value. This means that iCopy within the capturing function is not related to i in any way (except for the fact that they happen to have the same value at that point in time). If i changes later (which it does - on the next iteration of the loop) then iCopy is not affected.
This will work as we expect it to but the problem now is that the JavaScript interpreter will create an instance of the capturing function per loop iteration. It has to do this because it doesn't know if the function object will be modified elsewhere. Since functions are standard JavaScript objects, they can have properties like any other object, which could be changed in the loop. Thus by creating the function in the loop context, you cause the interpreter to create multiple function instances, which can cause unexpected behavior and performance problems. To fix the issue, we need to move the function out of the loop:
I would have liked to use Array.prototype.forEach here, like this
buttons.forEach(function(curButton) {
curButton.onclick = function(e) {
t.progressBars[t.current].update(curButton.getAttribute("data-value"));
};
});
Your two examples are not equivalent.
In the first, you are creating an anonymous function and calling it on every loop.
The inner function (the click event handler) is fine - you're assigning a new function - but it's the anonymous outer function that is inefficient in this context. In your second example the outer function is refactored out of the loop where is it only created once, instead of buttons.length times.

Why setInterval wont stop?

Why console.log(1) gets executed here forever:
var interval = setInterval(function() {
if (true) {
clearInterval(interval);
console.log(1);
}
}, 100);
It depends on the scope within which you're executing this code.
If interval is unique within its scope — be it global or function scope — then this will work as expected.
If, however, you execute this code within a loop (for example), then you are overwriting interval with some new interval on each iteration, breaking your clearInterval call for all but the very last setInterval call:
for (var i = 0; i < 3; i++) {
var interval = setInterval(function() {
if (true) {
clearInterval(interval);
console.log(1);
}
}, 100);
}
// ^ will give you one single console log entry,
// and two more console log entries per second forever
It's seems that your variable interval is used somewhere again. If I run code you provided it works as expected. I guess user Lightness has given a great explaination of this, also he provided piece of code where "closure problem" is obvious (which caused you problem too). I just want to add extra information. If you want your code inside of loop + setInteval works aparat you can do the following:
for (var i = 0; i < 3; i++) {
var o = {
i: i,
interval: null,
timer: function() {
if (true) {
clearInterval(this.interval);
console.log(this.i);
}
}
};
o.interval = setInterval(o.timer.bind(o), 1000);
}
DEMO
I hope it will be useful for someone.

Javascript scope and calling a function

My code:
for (var i = 0; i < mapInfos.length; i++) {
var x = function () { doStuff(i); };
google.maps.event.addListenerOnce(mapInfos[i].map, 'tilesloaded', x);
}
The doStuff method simply alerts the value of i. mapInfos has two entries, so you'd expect it to alert 0 and 1, but instead it alerts 2 and 2. I can appreciate vaguely why it is doing this (although var i should keep it local to the scope of the loop?) but how can I make it work as intended?
edit — note that when first posted, the original question included a link to a jsfiddle that seemed to be a relevant example of what the current question is trying to achieve, only it appears to work ...
The code in the jsfiddle works because there's only one "i" in that code. The "i" used in the second loop (where the functions are actually called) is the same "i" as used in the first loop. Thus, you get the right answer because that second loop is running "i" through all the values from zero through four again. If you added:
i = 100;
functions[0]();
you'd get 100 printed out.
The only way to introduce a new scope in JavaScript is a function. One approach is to write a separate "function maker" function:
function makeCallback(param) {
return function() {
doStuff(param);
};
}
Then in your loop:
for (var i = 0; i < mapInfos.length; i++) {
var x = makeCallback(i);
google.maps.event.addListenerOnce(mapInfos[i].map, 'titlesloaded', x);
}
That'll work because the call to the "makeCallback" function isolates a copy of the value of "i" into a new, unique instance of "param" in the closure returned.
Create a new scope for it.
Functions create scope.
function doStuffFactory(i) {
return function () { doStuff(i); };
}
for (var i = 0; i < mapInfos.length; i++) {
var x = doStuffFactory(i);
google.maps.event.addListenerOnce(mapInfos[i].map, 'tilesloaded', x);
}
Change it to
var x = function (param) { doStuff(param); };
Obviously what is going on is that you are alerting a variable that is changing. With the above change it copies it so even if i changes it will still alert the right value.
Javascript doesn't have block scope, so you don't get an x that's local to the loop. Yea!
It has function scope, though.
Yep, weird isn't it!Pointy has an explanation
I have no idea why your first example worked (I wasn't expecting it to) Pointy has an explanation of why your first example worked - The reason why your second one doesn't is because i is scoped to the function containing the for loop, not to the scope defined by the for loop. In fact the only things that have scope in JavaScript are functions. This means that by the time your function gets executed i is 2.
What you need to do is create a scope, for example:
for (var i = 0; i < mapInfos.length; i++) {
var x = (function() {
return function () { doStuff(i); };
})(i);
google.maps.event.addListenerOnce(mapInfos[i].map, 'tilesloaded', x);
}
See JavaScript closures in for-loops for more.

setTimeout in for-loop does not print consecutive values [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
I have this script:
for (var i = 1; i <= 2; i++) {
setTimeout(function() { alert(i) }, 100);
}
But 3 is alerted both times, instead of 1 then 2.
Is there a way to pass i, without writing the function as a string?
You have to arrange for a distinct copy of "i" to be present for each of the timeout functions.
function doSetTimeout(i) {
setTimeout(function() {
alert(i);
}, 100);
}
for (var i = 1; i <= 2; ++i)
doSetTimeout(i);
If you don't do something like this (and there are other variations on this same idea), then each of the timer handler functions will share the same variable "i". When the loop is finished, what's the value of "i"? It's 3! By using an intermediating function, a copy of the value of the variable is made. Since the timeout handler is created in the context of that copy, it has its own private "i" to use.
Edit:
There have been a couple of comments over time in which some confusion was evident over the fact that setting up a few timeouts causes the handlers to all fire at the same time. It's important to understand that the process of setting up the timer — the calls to setTimeout() — take almost no time at all. That is, telling the system, "Please call this function after 1000 milliseconds" will return almost immediately, as the process of installing the timeout request in the timer queue is very fast.
Thus, if a succession of timeout requests is made, as is the case in the code in the OP and in my answer, and the time delay value is the same for each one, then once that amount of time has elapsed all the timer handlers will be called one after another in rapid succession.
If what you need is for the handlers to be called at intervals, you can either use setInterval(), which is called exactly like setTimeout() but which will fire more than once after repeated delays of the requested amount, or instead you can establish the timeouts and multiply the time value by your iteration counter. That is, to modify my example code:
function doScaledTimeout(i) {
setTimeout(function() {
alert(I);
}, i * 5000);
}
(With a 100 millisecond timeout, the effect won't be very obvious, so I bumped the number up to 5000.) The value of i is multiplied by the base delay value, so calling that 5 times in a loop will result in delays of 5 seconds, 10 seconds, 15 seconds, 20 seconds, and 25 seconds.
Update
Here in 2018, there is a simpler alternative. With the new ability to declare variables in scopes more narrow than functions, the original code would work if so modified:
for (let i = 1; i <= 2; i++) {
setTimeout(function() {
alert(i)
}, 100);
}
The let declaration, unlike var, will itself cause there to be a distinct i for each iteration of the loop.
You can use an immediately-invoked function expression (IIFE) to create a closure around setTimeout:
for (var i = 1; i <= 3; i++) {
(function(index) {
setTimeout(function() { alert(index); }, i * 1000);
})(i);
}
This's Because!
The timeout function
callbacks are all running well after the completion of the loop. In fact,
as timers go, even if it was setTimeout(.., 0) on each iteration, all
those function callbacks would still run strictly after the completion
of the loop, that's why 3 was reflected!
all two of those functions, though they are defined
separately in each loop iteration, are closed over the same shared global
scope, which has, in fact, only one i in it.
the Solution's declaring a single scope for each iteration by using a self-function executed(anonymous one or better IIFE) and having a copy of i in it, like this:
for (var i = 1; i <= 2; i++) {
(function(){
var j = i;
setTimeout(function() { console.log(j) }, 100);
})();
}
the cleaner one would be
for (var i = 1; i <= 2; i++) {
(function(i){
setTimeout(function() { console.log(i) }, 100);
})(i);
}
The use of an IIFE(self-executed function) inside each iteration created a new scope for each
iteration, which gave our timeout function callbacks the opportunity
to close over a new scope for each iteration, one which had a variable
with the right per-iteration value in it for us to access.
The function argument to setTimeout is closing over the loop variable. The loop finishes before the first timeout and displays the current value of i, which is 3.
Because JavaScript variables only have function scope, the solution is to pass the loop variable to a function that sets the timeout. You can declare and call such a function like this:
for (var i = 1; i <= 2; i++) {
(function (x) {
setTimeout(function () { alert(x); }, 100);
})(i);
}
You can use the extra arguments to setTimeout to pass parameters to the callback function.
for (var i = 1; i <= 2; i++) {
setTimeout(function(j) { alert(j) }, 100, i);
}
Note: This doesn't work on IE9 and below browsers.
ANSWER?
I'm using it for an animation for adding items to a cart - a cart icon floats to the cart area from the product "add" button, when clicked:
function addCartItem(opts) {
for (var i=0; i<opts.qty; i++) {
setTimeout(function() {
console.log('ADDED ONE!');
}, 1000*i);
}
};
NOTE the duration is in unit times n epocs.
So starting at the the click moment, the animations start epoc (of EACH animation) is the product of each one-second-unit multiplied by the number of items.
epoc: https://en.wikipedia.org/wiki/Epoch_(reference_date)
Hope this helps!
You could use bind method
for (var i = 1, j = 1; i <= 3; i++, j++) {
setTimeout(function() {
alert(this);
}.bind(i), j * 100);
}
Well, another working solution based on Cody's answer but a little more general can be something like this:
function timedAlert(msg, timing){
setTimeout(function(){
alert(msg);
}, timing);
}
function yourFunction(time, counter){
for (var i = 1; i <= counter; i++) {
var msg = i, timing = i * time * 1000; //this is in seconds
timedAlert (msg, timing);
};
}
yourFunction(timeInSeconds, counter); // well here are the values of your choice.
I had the same problem once this is how I solved it.
Suppose I want 12 delays with an interval of 2 secs
function animate(i){
myVar=setTimeout(function(){
alert(i);
if(i==12){
clearTimeout(myVar);
return;
}
animate(i+1)
},2000)
}
var i=1; //i is the start point 1 to 12 that is
animate(i); //1,2,3,4..12 will be alerted with 2 sec delay
the real solution is here, but you need to be familiar with PHP programing language.
you must mix PHP and JAVASCRIPT orders in order to reach to your purpose.
pay attention to this :
<?php
for($i=1;$i<=3;$i++){
echo "<script language='javascript' >
setTimeout(function(){alert('".$i."');},3000);
</script>";
}
?>
It exactly does what you want, but be careful about how to make ralation between
PHP variables and JAVASCRIPT ones.

JavaScript Variable Scope [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I'm having a problem with some JavaScript code.
Script
setTimeout(function() {
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 200);
}
}, 200);
Outputs
5, 5, 5, 5, 5 instead of 1, 2, 3, 4, 5
I can kind of understand why this doesn't work, but I was wondering if someone could explain to me what's happening and why it's not working!
Also, how can this scope problem be overcome?
The setTimeout callback functions are executed asynchronously, all the console.log calls you make refer to the same i variable, and at the time they are executed, the for loop has ended and i contains 4.
You could wrap your inner setTimeout call inside a function accepting a parameter in order to store a reference to all the i values that are being iterated, something like this:
setTimeout(function() {
for (var i = 0; i < 5; i++) {
(function (j) { // added a closure to store a reference to 'i' values
setTimeout(function() {
console.log(j);
}, j * 200);
})(i); // automatically call the function and pass the value
}
}, 200);
Check my answer to the following question for more details:
Variables in Anonymous Functions — Can someone explain the following?
Take a look at this question. It might help you understand the scope and closures better, very similar to your question.
You're trying to create a closure containing the variable "i". But closures are only created at the end of a function. So if your functions are created in a for loop, they will all have the values from the last iteration.
You can fix it with something like this:
var createFunction = function(index) {
return function() {
console.log(index);
}
};
for (var i = 0; i < 5; i++) {
setTimeout(createFunction(i), i * 200);
}
where you return the function from another function.
The variable i exists in the scope of the outer function.
It changes as the loop runs.
The inner function references it.
Try something like this:
var i_print_factory = function (value) {
return function () {
console.log(value);
};
};
var init_timers = function () {
for (var i = 0; i < 5; i++) {
setTimeout(i_print_factory(i), i * 200);
}
};
setTimeout(init_timers, 200);
Because you are accessing the same variable i in all the functions used in set timeout. The setTimeout function sets the function to fire the number of milliseconds in the future on the same thread as the i variable. The i value isn't copied in the function, the function is referencing the actual variable i when it is fired. Because you have looped through parent function until the i = 5 and this is done before anything else has a chance to fire, they all show up as 5.

Categories