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?

Dee2000
  • 1,641
  • 2
  • 18
  • 21
  • See http://stackoverflow.com/questions/3445855/javascript-passing-an-object-to-a-settimeout-function-inside-a-loop – kennytm Jun 21 '11 at 12:17
  • 1
    possible duplicate of [Javascript closure inside loops - simple practical example](http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) – Felix Kling Mar 10 '13 at 08:28

5 Answers5

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.

pimvdb
  • 151,816
  • 78
  • 307
  • 352
Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • The function does't have to return anything, you can have the setTimeout in an `IIFE` – qwertymk Jun 21 '11 at 12:24
  • @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 Jun 21 '11 at 12:25
  • @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 Jun 21 '11 at 12:32
  • D'oh!! I should have known this, I've used that (function(){})() syntax before. THANKS !! – Dee2000 Jun 21 '11 at 12:33
  • @qwertymk I beg to differ - if you google for "javascript loop closure" almost all results show an IIFE that returns a function. – Alnitak Jun 21 '11 at 12:34
  • @Alnitak: From reading Alman, Irish and Resig and other's blogs, I always see it the other way – qwertymk Jun 21 '11 at 12:35
  • @qwertymk do you have any links to articles (seriously, would like to see what they have to say about it). – Alnitak Jun 21 '11 at 12:38
  • Not on hand, but if you look you can prob find some – qwertymk Jun 21 '11 at 12:40
  • 1
    @Alnitak: http://benalman.com/news/2010/11/immediately-invoked-function-expression/ – qwertymk Jun 21 '11 at 12:52
  • 2
    @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. – user113716 Jun 21 '11 at 13:01
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;
}
Svante Svenson
  • 12,315
  • 4
  • 41
  • 45
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;
}
Pointy
  • 405,095
  • 59
  • 585
  • 614
  • 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. – Dee2000 Jun 21 '11 at 12:35
  • 1
    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. – Pointy Jun 21 '11 at 12:39
  • Wow, i think this is the ***Best*** Answer – iConnor Jun 22 '13 at 23:20
  • why don't you just pass as the `delay` -> `(i+1) * 1000` ? seems to me it's better than an extra variable – vsync Oct 22 '13 at 12:40
  • @vsync yes that'd certainly work. (sorry I misunderstood you at first.) – Pointy Oct 22 '13 at 13:09
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

Ciaran
  • 2,174
  • 1
  • 15
  • 17
-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;
}
Subdigger
  • 2,166
  • 3
  • 20
  • 42