I'm building my first project with NodeJS in these days but I'm a bit confused on a task I believe it's a simple one, I guess the problem is my lack of knowledge of these async approach but I cannot find the answer anywhere.
I've a simple loop iterating over an array and for any element, based on some rule, I'll call a function or another. Now some operation will be faster than others so I could end up with a function on element N returning sooner than the function on element N-1. To make it simple something like this
for (var i = 0 ; i < 10 ; i++) {
if (i%2 === 0) {
setTimeout(function(i) {
console.log(i);
}, 2000);
}
else { console.log(i); }
}
so any even number will be printed with 2 seconds lag while the odd numbers will be printed immediately. Anyway running it I get
1
3
5
7
9
<<2 seconds break>>
undefined
undefined
undefined
undefined
undefined
looks like the even value is "lost". How can I pass the value making sure the function won't lose the input value? Am I missing something?
Thanks, Mauro
Your setTimeout
argument function is declared to take a single parameter, i
. When setTimeout
calls the function with no arguments, as it does, the parameter is thus set to undefined
.
This would seem to be be slightly better, as it no longer shadows the outer i
variable you were originally trying to reference...
setTimeout(function() {
console.log(i);
}, 2000)
...but if you run it, you'll find it prints 10
5 times, because every function you create is referencing the same i
variable, whose value will be 10
when the loop exit condition becomes true
and it terminates.
Creating a closure which holds the value of i
as it was during the loop in which each setTimout
argument function was created will do the trick:
setTimeout((function(i) {
return function() {
console.log(i);
}
})(i), 2000)
Renaming the argument to the Immediately-Invoked Function Expression we just used might help make things clearer:
setTimeout((function(loopIndex) {
return function() {
console.log(loopIndex);
}
})(i), 2000)
We're:
i
(which is not an object, so is effectively passed by value).setTimeout
.This works because:
Creating a function in JavaScript creates a new scope to hold the function's argument variables and any other variables declared within it using the var
keyword. Think of this like an invisible object with properties corresponding to the variable names.
All functions hold a reference to the scope in which they were defined, as part of their scope chain. They still have access to this scope even if it's no longer "active" (e.g. when the function it was created for returns). The "inner" function was created in a scope which contained a loopIndex
variable, set to the value of i
at the time the IIFE was called. So when you try to reference a variable named loopIndex
from inside the inner function, it first checks its own scope (and doesn't find a loopIndex
there) then starts walking up its scope chain - first, it checks the scope in which it was defined, which does contain a loopIndex
variable, the value of which is passed to console.log()
.
That's all a closure is - a function which has access to the scope in which it was defined, even if the function the scope was created for has finished executing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With