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

Dee2000 picture Dee2000 · Jun 21, 2011 · Viewed 13.8k times · Source

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?

Answer

Alnitak picture Alnitak · Jun 21, 2011

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.