17

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?

2

5 Answers 5

31

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.

Sign up to request clarification or add additional context in comments.

10 Comments

The function does't have to return anything, you can have the setTimeout in an IIFE
@qwertymk that's true, but I prefer to only wrap the callback, not the registration of the callback, not least because it's what's normally needed when doing the same for DOM event handlers.
@Alnitak: I think it's pretty standard to do it svinto's way. It's also easier to understand without giving the reader's brain another level of scope
@qwertymk, @Alnitak: With either approach, you're repeatedly creating a new, yet identical, function instance inside of a loop. Better IMO is to create a named function outside the loop, and call that.
|
28

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;
}

Comments

15

You could write a separate function to set up the timeout:

function doTimer(str, delay) {
  setTimeout(function() { alert(str); }, delay);
}

Then just call that from the loop:

var delay = 1000;
for(var i=0;i<strings.length;i++) {
    doTimer(strings[i], delay);
    delay += 1000;
}

5 Comments

I maybe wrong, but that looks to me like it might suffer from the same problem. It wouldn't create a closure to the str variable, or would it? Either way, it leaves a global doTimer function behind, so I prefer Alnitak's answer. Thanks though.
It passes a copy of the reference, so it's fine. The trouble in the original is that there's only one "i" shared by all the timeout handlers. And the function doesn't have to be global - it can be a local function in whatever function that other code is in.
Wow, i think this is the Best Answer
why don't you just pass as the delay -> (i+1) * 1000 ? seems to me it's better than an extra variable
@vsync yes that'd certainly work. (sorry I misunderstood you at first.)
0

Although not as backward compatible as some of the other answers, thought I'd throw up another option.. this time using bind()!

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

View demo of it in action

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.