Use IIFE in setTimeout in a loop, but why? [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
I know it is a classic js question:
(My question is not how to solve this problem, but how IIFE solve this problem. Thanks for the other answer link but I didn't find the answer I want)
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
},1000)
}
This will print out five consecutive 5, and one way to avoid that is to create IIFE in setTimeout, I know it creates a closure but still why? Can someone give a more specific explanation about it?
Also why can't I just pass a parameter to the function?
for(var i = 0; i < 5; i++) {
setTimeout(function(i) {
console.log(i);
},1000)
}
This prints out 5 undefined...I got more confused, why is that?

If I understand your question correctly, you want to print 0...4. You can achieve that by using let instead of var which creates a new binding of i for each loop iteration [1], [2]:
// Prints 0, 1, 2, 3, 4:
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
Your suggestion to add an argument i to the callback fails as the calling function setTimeout doesn't pass anything to the callback. Thus the i argument is undefined.
Alternatively, use the classic IIFE approach:
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, 1000);
})(i);
}
Even better, of course, would be to move the for-loop into the setTimeout callback. But I assume you chose this code for demonstration purposes only.

Related

I get strange output when i use var keyword in for loop with setTimeOut inside [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 2 years ago.
I have this code.
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
I don't understand the output from this lines of code:
The output in the console is number 6,and it says that is repeated five times.
If i use the let keyword for "i" then i get the output that i expect,
1,2,3,4,5 after one second
Why is that ?
var has scoping issues and that's why let was introduced.
In your for loop you are defining i, but actually it's stuck to the global scope, and after 1 second, the for loop would actually be done, and when the setTimeout callback is fired, i would have already reached 6 and it's read from the global scope.
In a nutshell, because i is stuck to the upper scope of for, each iteration modifies the calue of i and doesn't create another i.
If you change var to let, the issue is resolved.
setTimeout is async hence will execute after the loop is done, that s why you have i=6, put it in a self invoking function to retain the value of i or use let instead of var in your loop
for (var i = 1; i <= 5; i++) {
((i) => setTimeout(function() {
console.log(i);
}, 1000))(i)
}
for (let i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}

Arrow function closure within for loop [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
Given this:
var m = 5;
for (i = 0; i < m; i++) {
add_row('row-'+(i+1), () => f(i))
}
if I do an alert in my f the it will always output the value 5. I believe this is due to the same problem mention here for python:
lambda function don't closure the parameter in Python?
How is this problem solved in javascript?
This is because add_row() is asynchronous. It calls the callback you pass it sometime LATER when the operation finishes. Meanwhile, the for loop has already finished. One simple solution in ES6, is to use let with your for loop. This creates a new and separate i variable for each run of the loop so when the callback is called sometime LATER, the variable i for that invocation of the loop is still at it was when the function was started.
If you were to insert some console.log() statements into your code like this:
var m = 3;
console.log("A");
for (i = 0; i < m; i++) {
console.log("B" + i);
add_row('row-'+(i+1), () => {
console.log("C" + i);
f(i);
})
}
console.log("D");
What you would see in the console is this:
A
B0
B1
B2
D
C0
C1
C2
Notice how "D" comes before any of the "Cx" lines. That shows you how the asynchronous callback is called LATER after your for loop is done executing.
Please study that and when you fully understand the reasoning for that order, then you will finally understand an asynchronous callback. Technically, the C0, C1 and C2 will be at the end, but could be in any order relative to each other.
Here's how you can use let for the for loop to create a separate variable i for each iteration of the loop so you will still have the appropriate value of i when the callback is called some time later:
for (let i = 0; i < m; i++) {
add_row('row-'+(i+1), () => f(i))
}
Before ES6, one could fix this by creating an extra closure which creates a new function scoped variable to "remember" the loop index separately for each invocation:
for (var i = 0; i < m; i++) {
(function(index) {
add_row('row-'+(index+1), () => f(index))
})(i);
}

IIFE and variable assignment in Javascript. Do we really need an IIFE? What is going on? [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
Say I have this function:
function printFruits(fruits) {
for (var i = 0; i < fruits.length; i++) {
setTimeout( function() {
console.log( fruits[i]);
}, i * 1000);
}
}
printFruits(["Lemon", "Orange", "Mango"])
So this returns undefined 3 times.
I can see on a high level that since variables are stored not by value but by reference inside the closure... the loop is finishing first and by the time the functions are dequeued from maybe the Event Loop... the variable is already at undefined (fruits.length evaluates to 3 which is too high for this array size). But why does this perform strangely... it prints "apple" 3 times.
function printFruits(fruits) {
for (var i = 0; i < fruits.length; i++) {
var someConstant = i;
setTimeout( function() {
console.log( fruits[someConstant]);
}, someConstant * 100);
}
}
printFruits(["mango", "banana", "apple"])
Shouldn't someConstant be changing as well with i? Why does it seem to be 2 always?
Also this works:
function printFruits(fruits) {
for (var i = 0; i < fruits.length; i++) {
(function() {
var current = i;
setTimeout( function() {
console.log( fruits[current]);
}, current * 1000);
})();
}
}
Why is the IIFE necessary to fix this problem?
2nd Example
function printFruits(fruits) {
for (var i = 0; i < fruits.length; i++) {
var someConstant = i;
setTimeout(function() {
console.log(fruits[someConstant]);
}, someConstant * 1000);
}
}
printFruits(["Lemon", "Orange", "Mango"])
This logs thrice Mango. Because everytime the someConstant variable is created and re-initialised to i. Recollect how for loop works. i-value is increasing here till 4, checks the condition 4<3, and terminates. So the matter inside the loop executes only thrice. So the last value of someConstant defined in the printFruits functional scope is 2. So when the inner function executes someConstant, its equal to 2. So we get each time Mango.
3rd example
function printFruits(fruits) {
for (var i = 0; i < fruits.length; i++) {
(function() {
var current = i;
setTimeout(function() {
console.log(fruits[current]);
}, current * 1000);
})();
}
}
printFruits(["Lemon", "Orange", "Mango"])
Here the beauty of closures happening. Here its a self executing function being executed, immediately. So when i = 1, it invokes immediately. Every function has a different scope now. There is a seperate current value defined for each. So later when it executes, it recollects whats the value of 'current' when it was defined inside its scope.
The only difference between these samples is that the for loop increments i to 3 before stopping, while the last value which is assigned to someConstant is 2. Your "working" code is outputting Mango three times (index 2 of the array) instead of undefined (index 3 of the array). The general behaviour is the same.
Yes, you do need an IIFE, or ES6's let keyword instead of var.
The difference is that someConstant never gets incremented after the last iteration. The for() loop sets i = 3, the test i < fruits.length fails, so the loop stops. As a result, someConstant is still set to 2 from the last iteration of the loop. Then all the callbacks run, so they all log fruits[2], which is Mango.
You need the IIFE to get each iteration to save its value of i in the closure.

Passing values to a function in javascript when using setTimeout [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
I am trying to set several timeouts inside a loop where the parameter function of setTimeout uses diferent values (depending of the loop index) as parameter. This is a simplificated example:
for(i=0; i<5; i++)
{
var m = setTimeout( function () {console.log(i)}, (i+1)*2000 );
}
I thought that with the code above I get "0, 1, 2, 3, 4" every 2 seconds. Instead of this I get "5, 5, 5, 5, 5" every 2 seconds. Why?
If you're happy to restrict support to modern web browsers (i.e. not IE 9 and earlier) the below will work:
for(i=0; i<5; i++)
{
var m = setTimeout( function (i) {console.log(i)}, (i+1)*2000, i );
}
You can pass your variable as a third argument to setTimeout and then receive it in your setTimeout function.
As for why your original code doesn't work, it has to do with Javascript scope which is explained quite well here: What is lexical scope?
You need a wrapper function to create a closure and keep value of i at the moment of iteration:
for(i=0; i<5; i++) {
(function(timeout) {
var m = setTimeout( function () {console.log(timeout)}, (timeout+1)*2000 );
})(i)
}

setTimeout not working as expected [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
How do JavaScript closures work?
(86 answers)
Closed 7 years ago.
I'm writing a for loop in Javascript. The desired goal is to print out 0, 1, 2 with a 3 second gap in between.
for (var i=0; i<3; i++) {
console.log(i);
}
This prints everything out as expected, with no pause. But when I add in a setTimeout:
for (var i=0; i<3; i++) {
setTimeout(function() {console.log{i},3000*i}
}
The result is that it prints out 3, 3, 3 with a 3 second gap. The pause worked, but it looks like its completing the loop before the right numbers can get printed.
You're exactly right that the loop is getting completed before the setTimeout calls run. Since all of your timeout functions reference i, they're all going to print out 3. The way to fix this is to capture the value of i in a closure.
for (var i = 0; i < 3; i++) {
(function(index) {
setTimeout(function() {
console.log(index);
}, 3000 * index);
})(i); // Instantly call the function and pass the value of i
}

Categories