Reputation: 16025
I'm trying to set five staggered function calls (happening one second apart). That part works fine. What doesn't work is, I can't pass values 0 through 4 into the callback function. It just passes '5' each time. I can't seem to figure out why and how to fix it.
Code:
function callback(num)
{
console.log(num);
}
for (var i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000)
setTimeout(function() { callback(i); }, loadDelay);
Result:
5
5
5
5
5
Desired result:
0
1
2
3
4
Upvotes: 7
Views: 14402
Reputation: 35309
You needed a closure in order to pass i
due to variable scoping. Check out this article, and this one as well for some good information on closures.
function callback(num)
{
console.log(num);
}
for (var i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000)
setTimeout((function(num){return function(){
callback(num);
}
})(i), loadDelay);
Upvotes: 4
Reputation: 6378
setTimeout creates some odd scoping problems. Frame.js was designed to resolve some of this kind of confusion, this also works [updated]:
function callback(num) {
console.log(num);
}
for (var i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000) {
Frame(function(next, i){
setTimeout(function() { callback(i); }, loadDelay);
next();
}, i);
}
Frame.init();
Upvotes: 0
Reputation: 25322
That's because you create a closure. So the function you pass to setTimeout
share the same i
instances. In the browser that supports the standards (not IE) you could have:
setTimeout(callback, loadDelay, i);
See: http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#timers
Otherwise you have to actually bind
the argument to the function:
setTimeout(callback.bind(undefined, i), loadDelay);
See: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
If the browser doesn't support ES5 bind
method, you can either implement the shim present in the link above, or manually doing something like:
setTimeout(function(index){
return function() { callback(index) }
}(i), loadDelay);
But I would say it's more readable using bind
and it's worthy to implement the shim. You can actually use this: https://github.com/kriskowal/es5-shim
To add es5 capabilities (where is possible) in the browser that don't support es5 natively.
Upvotes: 14
Reputation: 754545
Use a lambda / function expression to capture the current value. For example
for (var i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000) {
var doCall = function (j) {
setTimeout(function() { callback(j); }, loadDelay);
}
doCall(i);
}
The problem here is that there is only 1 i
value for all iterations of the loop. Variables in javascript have function scope even though you can declare them inside of a block. This means i
is alive for the entire function.
To illustrate the problem consider the below code executes exactly the same as your sample
var i;
for (i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000) {
...
}
My solution works because it introduces a new function and hence a new variable lifetime for j
. This saves the current value of i
in the function for use in the setTimeout
callback
Upvotes: 6