Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing functions to setTimeout in a loop: always the last value?

I'm trying to use setTimeout to execute an anonymous function that I pass information into and I'm having trouble. This (hard-coded version) would work just fine:

setTimeout(function(){alert("hello");},1000);
setTimeout(function(){alert("world");},2000);

But I'm trying to take the hello and world from an array and pass them into the function without (a) using global variables, and (2) using eval. I know how I could do it using globals or eval, but how can I do it without. Here is what I'd like to do (but I know it won't work):

var strings = [ "hello", "world" ];
var delay = 1000;
for(var i=0;i<strings.length;i++) {
    setTimeout( function(){alert(strings[i]);}, delay);
    delay += 1000;
}

Of course strings[i] will be out of context. How can I pass strings[i] into that anonymous function without eval or globals?

like image 897
Dee2000 Avatar asked Jun 21 '11 12:06

Dee2000


People also ask

How does setTimeout work in loop?

The setTimeout function callback isn't triggered until the for loop execution has completed. When the for loop has finished executing the value of i is 5. Now when the setTimeout call begins to execute it uses the last set value of i which is 5. Hence 5 is printed in all the setTimeout callbacks.

What is the default value of setTimeout?

First of all, the setTimeout JavaScript method should contain the function that you intend to apply. The second parameter sets a time when the specified function is to be called. However, it is optional, and the default value is 0.

Is setTimeout guaranteed?

setTimeout is a guarantee to a minimum time of execution. I wrote two gists that you can copy and paste into the console to check this.

Does setTimeout repeat?

setTimeout allows us to run a function once after the interval of time. setInterval allows us to run a function repeatedly, starting after the interval of time, then repeating continuously at that interval.


2 Answers

This is the very frequently repeated "how do I use a loop variable in a closure" problem.

The canonical solution is to call a function which returns a function that's bound to the current value of the loop variable:

var strings = [ "hello", "world" ];
var delay = 1000;
for(var i=0;i<strings.length;i++) {
    setTimeout(
        (function(s) {
            return function() {
                alert(s);
            }
        })(strings[i]), delay);
    delay += 1000;
}

The outer definition function(s) { ... } creates a new scope where s is bound to the current value of the supplied parameter - i.e. strings[i] - where it's available to the inner scope.

like image 67
Alnitak Avatar answered Sep 19 '22 20:09

Alnitak


Just add a scope around the setTimeout call:

var strings = [ "hello", "world" ];
var delay = 1000;
for(var i=0;i<strings.length;i++) {
    (function(s){
        setTimeout( function(){alert(s);}, delay);
    })(strings[i]);
    delay += 1000;
}
like image 26
Svante Svenson Avatar answered Sep 21 '22 20:09

Svante Svenson