javascript for-loop and timeout function - javascript

I am facing an issue with a for-loop running a setTimeout.
for (var x = 0; x < 5; x++) {
var timeoutFunction = function() {
return function() {
console.log(x)
}
}
setTimeout(timeoutFunction(), 1)
}
I expect the output
0
1
3
4
Yet, for some reason they all output 5.
Variable x is defined in the local scope of the for-loop, so I thought this may not count for the callback of setTimeout. I tested with defining x outside of the for-loop.
var x = 10
for (var x = 0; x < 5; x++) {
var timeoutFunction = function() {
return function() {
console.log(x)
}
}
setTimeout(timeoutFunction(), 1)
}
I figured this output would be giving 10, yet it didn't. Then I thought it would make sense to define x afterwards.
for (var x = 0; x < 5; x++) {
var timeoutFunction = function() {
return function() {
console.log(x)
}
}
setTimeout(timeoutFunction(), 1)
}
var x = 10
This does return only 10. This implies the callbacks are all called after the for-loop is executed? And why do they only conform to the parent scope of the for-loop once the variable is initialised after execution of the for-loop? Am I missing something?
I know how make this example working with
for (var x = 0; x < 5; x++) {
var timeoutFunction = function(x) {
return function() {
console.log(x)
}
}
setTimeout(timeoutFunction(x), 1)
}
Yet, I really wonder what is missing...

Note that specifying 1 as the delay value will not actually cause the function to execute after 1 millisecond. The fastest the function could execute is roughly 9 milliseconds (and that's based on the internals of the browser), but in reality, it can't run the function until there is no other code running.
As for the unexpected output, you are experiencing this behavior because the timeoutFunction includes a reference to the x variable that is declared in a higher scope. This causes a closure to be created around the x variable, such that each time the function runs it does not get a copy of x for itself, but it is sharing the x value because it is declared in a higher scope. This is the classic side effect of closures.
There are a few ways to adjust the syntax to fix the issue...
Make a copy of x and let each function use its own copy by passing x into the function. When you pass a primitive type (boolean, number, string) a copy of the data is created and that's what is passed. This breaks the dependence on the shared x scope. Your last example does this:
for (var x = 0; x < 5; x++) {
var timeoutFunction = function(x) {
return function() {
console.log(x)
}
}
// Because you are passing x into the function, a copy of x will be made for the
// function that is returned, that copy will be independent of x and a copy will
// be made upon each loop iteration.
setTimeout(timeoutFunction(x), 1)
}
Another example that does the same thing would be needed if your timeout function wasn't returning another function (because there would be no function to pass the value to). So, this example creates an extra function:
for (var x = 0; x < 5; x++) {
// This time there is no nested function that will be returned,
function timeoutFunction(i) {
console.log(i);
}
// If we create an "Immediately Invoked Funtion Expression" (IIFE),
// we can have it pass a copy of x into the function it invokes, thus
// creating a copy that will be in a different scope than x.
(function(i){
setTimeout(function(){
timeoutFunction(i); // i is now a copy of x
}, 1);
}(x));
}
If you are working with browsers that support the ECMAScript 2015 standard, you can simply change the var x declaration in your loop to let x, so that x gets block level scope upon each iteration of the loop:
// Declaring a variable with let causes the variable to have "block-level"
// scope. In this case the block is the loop's contents and for each iteration of the
// loop. Upon each iteration, a new "x" will be created, so a different scope from
// the old "x" is what's used.
for (let x = 0; x < 5; x++) {
var timeoutFunction = function() {
return function() {
console.log(x)
}
}
setTimeout(timeoutFunction(), 1)
}

You have to create new scope for your function to make it 'remember' value from given iteration:
for (var x = 0; x < 5; x++) {
var timeoutFunction = (function(x) {
return function() {
console.log(x)
}
})(x)
setTimeout(timeoutFunction(), 1)
}
Another solution is to use ES2015 let:
for (let x = 0; x < 5; x++) {
var timeoutFunction = function() {
console.log(x)
}
setTimeout(timeoutFunction(), 1)
}

use this snippet
for(let x = 0; x < 5; x++) {
var timeoutFunction = function() {
console.log(x);
};
setTimeout(timeoutFunction, 1);
};
console.log('For loop completed');
JavaScript is not multi threaded so with your code the timeoutFunction will execute after the for loop is completed since you are using the global variable (with respect to timeoutFunction context) the final result is printed

Related

JS: Default function parameter values and scope

I'm a bit confused about how scope & variable assignment within a function seems to change when assigning a default parameter value for that function.
For example, when this function has a default value assigned to parameter i, the output array variable appears to be block-scoped when inspected with Chrome Dev Console,:
function steps(n, i = 40) {
var output = [n];
}
steps(10, 20);
However, by removing the default parameter value for i, the output array variable is scoped locally:
function steps(n, i) {
var output = [n];
}
steps(10, 20);
Why does assigning a default value to parameter i affect the scope of the output array variable?
I was initially made aware of this shift in function scope by attempting to run the following segment of code through pythontutor.com's live programming environment for Javascript. Even though the code executes as expected within an IDE, it fails to run due to scope issues on pythontutor:
function steps(n, i = 1) {
// declare base case
if (n === 0)
return;
var output = [];
print(i, "#");
print(n - 1, " ");
console.log(output.join(""));
// make recursive call
steps(n - 1, i + 1);
function print(num, char) {
for (let j = 0; j < num; j++) {
output.push(`${char}`);
}
}
}
steps(3);
The pythontutor processor halts execution three steps in, at the invocation of print() just after declaring the output variable. Pythontutor.com will, however, execute the code as expected if I declare the output variable globally first:
var output = [];
function steps(n, i = 1) {
// declare base case
if (n === 0)
return;
output = [];
print(i, "#");
print(n - 1, " ");
console.log(output.join(""));
// make recursive call
steps(n - 1, i + 1);
function print(num, char) {
for (let j = 0; j < num; j++) {
output.push(`${char}`);
}
}
}
steps(3);
It's because default initialisers run in their own scope. Only if there are none, the body code is evaluated in the top function scope. It would only make a difference if you put a function expression in a default initaliser, which may close over the other parameters but does not have access to the variables that will be declared in the body.
Basically it's the difference between
function steps() {
var n = arguments[0],
i = arguments[1];
var output = [n];
}
and
function steps() {
var n = arguments[0],
i = arguments.length > 0 ? arguments[1] : 40;
(() => {
var output = [n];
}());
}

Closure Inside For Loop

I know that one of the ways to log 0 to 9 with this code:
EDIT: Source
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}), 10)
}
jsfiddle
is to make setTimeout self invoking and pass i as a parameter, like so:
for(var i = 0; i < 10; i++) {
setTimeout((function(i) {
console.log(i);
})(i), 10)
}
but I've tested making setTImeout self invoking without passing i, and it still works:
for(var i = 0; i < 10; i++) {
setTimeout((function() {
console.log(i);
})(), 10)
}
jsfiddle
My questions:
Why does it work even without passing i as a parameter?
Is it necessary to pass i?
Problem
It's not a Closure.
for(var i = 0; i < 10; i++) {
setTimeout((function(i) {
console.log(i);
})(i), 10)
}
setTimeout expects actually a function as parameter, but you made it invoking immediately. So it logs immediately the value of i, without waiting. setTimeout has now the return of your anonymous function as first parameter which is undefined.
The same here:
for(var i = 0; i < 10; i++) {
setTimeout((function() {
console.log(i);
})(), 10)
}
It's executed immediately, it doesn't wait for the 10ms. The only difference you didn't passed i as parameter, but it will look in the parent scope for a variable called i – and there is one.
You will see that your anonymous function is called immediately if you set the time to e.g. one second (1000).
Solution
A real closure would look like this:
Without parameter: You will see 10 times 10, because at time of execution of the inner function the loop is already finished which means i equals 10 at that time:
for(var i = 0; i < 10; i++) {
(function() {
setTimeout(function() {
console.log(i);
},10);
})();
}
With parameter – you will get the expected result:
for(var i = 0; i < 10; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
},10);
})(i);
}
In all your avobe code, you are executing the function given as the first parameter in setTimeout without returning anything. So what happens is you are returning undefined.
In the code below, I am also self-invoking, but returning a function for setTimeout to execute after a specific intervali * 10.
This is the closure function. Check the console logs.It prints the time stamp in miliseconds. There is a gap of 10ms.
for (var i = 0; i < 10; i++) {
setTimeout((function(i) {
return function() {
console.log(i, performance.now() + 'ms');
}
})(i), i * 10)
}
Additional Info
In the comment section, I have a query
what's the point, when you could just put the setTimeout inside an anonymous function?
So I guess this is the code you are suggesting.
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i, performance.now() + 'ms');
}, i * 10)
}
If you run this snippet, you will get a log of 10 for every log. This is the beauty of closures. When you are executing the annonymous function
function() {
console.log(i, performance.now() + 'ms');
}
It cant find i in current scope and searches it parent scope. And voila, it finds i, but unfortunately the loop has completed its iteration by that time and its value = 10 now. So logged 10 every time.
This is why we need closure.

javascript closure in loop

Following code is given:
var a = [ ], i = 0, j = 0;
for (i = 0; i < 5; i += 1) {
(function(c) {
a.push(function () {
console.log(c); });
})(i);
};
for (j = 0; j < 5; j += 1) { a[j](); }
Why does i always get bigger by 1 instead of staying 5? Hasn't the foor loop already been passed, so the i parameter given to the anonymous function should be 5?
If you referenced i from the inner closure then yes, you would see the result being 5 in all cases. However, you pass i by value to the outer function, which is accepted as parameter c. The value of c is then fixed to whatever i was at the moment you created the inner closure.
Consider changing the log statement:
console.log("c:" + c + " i:" + i);
You should see c going from 0 to 4 (inclusive) and i being 5 in all cases.
chhowie's answer is absolutely right (and I upvoted it), but I wanted to show you one more thing to help understand it. Your inner function works similarly to a more explicit function call like this:
var a = [ ], i = 0, j = 0;
function pushFunc(array, c) {
array.push(function () {
console.log(c);
});
}
for (i = 0; i < 5; i += 1) {
pushFunc(array, i);
}
for (j = 0; j < 5; j += 1) { a[j](); }
Which should also help you understand how c comes from the function argument, not from the for loop. Your inner function is doing exactly the same thing as this, just without an externally declared named function.

Why is this javascript for loop running only once?

function f1() {
for (i = 0; i <= 5; i++)
console.log(i);
}
function foo() {
for (i = 0; i < 5; i++)
f1();
}
foo();
Hi, I'm trying to understand why the result of executing foo is:
0
1
2
3
4
5
And not:
0
1
2
3
4
5
0
1
2
3
4
5
0
1
2
3
4
5
0
1
2
3
4
5
0
1
2
3
4
5
It's a slide I am reading about JS and its talking about when you don't use var then it is defined on the global object and provides this example without any further details why we get the result.
I thought it will simply loop and run the f1 function each time until its less than 5.
Please help me understand.
Thanks
The problem is in your iterators (i):
for (i = 0; i <= 5; i++)
i is global, and both your for loops test against it, making them only run once, and aborting when i == 5.
So, what happens is this:
When you call foo(), foo tells the js interpreter to create a variable in the global scope called i, and set it to 0. Then foo calls f1.
There, the for loop sets i, which already exists, to 0, and runs it's loop like it should, incrementing i up to 5.
Then, it's time for the second iteration of the loop in foo, so it checks if i < 5, but it's not (i==6 (5 from f1, +1 from foo)), so it will not call f1 again.
To fix this, either declare them in the function's local scope using var:
function f1() {
for (var i = 0; i <= 5; i++)
console.log(i);
}
function foo() {
for (var i = 0; i < 5; i++)
f1();
}
foo();
Or, use different variables:
function f1() {
for (i = 0; i <= 5; i++)
console.log(i);
}
function foo() {
for (j = 0; j < 5; j++)
f1();
}
foo();
However, this second option is a bad idea, since it will both place i and j in the global scope, which is asking for conflicts. I'd suggest using the var option.
If you write your loop like
for (i = 0; i < 5; i++)
here i refers to a global variable, because you have not declared it with the var keyword, and since you use i in both functions, they end up actually using the same variable.
You should then replace both loops with
function f1() {
for (var i = 0; i <= 5; i++)
console.log(i);
}
function foo() {
for (var i = 0; i < 5; i++)
f1();
}
foo();
As your code is originally written, i is a shared global variable, so foo starts, sets is to 0 then calls f1 which loop on it up to 6, when control returns to foo it founds that i is 6, so i < 5 is false and so it ends the loop.

Looping through multiple variables in a function

I've written a function that contains an if statement and the condition contains a variable.
function interval() {
var interval = setInterval(function() {
if (x < max) {
x = parseInt(x) + 1;
$('.max').html(addCommas(x));
}
}, 1);
};
So here max is my variable. Works great, but now I have three more variables and I want to run the same function but with the different variables each time. I could copy the function 3 more times and edit the variable in each one but that strikes me as highly inefficient.
What would be the best way to achieve this Stacked?
Pass max as an argument to the function
function interval(max){
var x = 0; //always declare variables
var interval = setInterval( function(){
if (x < max){
x = parseInt(x) + 1;
$('.max').html(addCommas(x));
}
}, 1);
};
I'm not sure of the driver for putting this code in an interval. You may want to consider using a for loop:
function interval(max){
for(var x = 0; x < max; x++){
$(".max").html(addCommas(x));
}
};
Use the parameters in function and use same function.
function interval(vfield){
var interval = setInterval( function(){
if (x < vfield){
x = parseInt(x) + 1;
$('.max').html(addCommas(x));
}
}, 1);
};
Where vfield is your variable.

Categories

Resources