0
var animel = new Array();

animel[0] = 'cat';
animel[1] = 'dog';
animel[2] = 'horse';
animel[3] = 'cow';
animel[4] = 'elephant';
animel[5] = 'tiger';
animel[6] = 'lion';
animel[7] = 'fish';

for (var i = 0; animel.length > i; i++) {
    setTimeout( function () {

        console.log(animel[i]);

    }, 2000);

}

When I execute this code in console, it logs undefined instead of the name of elements. What am I doing wrong in this?

5 Answers5

3

A very common problem: the callback is executed asynchronously, but uses the last set value of i. The chain of events is:

  • your loop sets a number of timeouts
  • the loop ends, i has the value 8
  • the timeouts fire, and execute console.log(animel[i]), where i is 8

To avoid that you need to break the closure connection to i:

setTimeout((function (index) {
    return function () { console.log(animel[index]); }
})(i), 2000);
deceze
  • 510,633
  • 85
  • 743
  • 889
2

The functions inside the setTimeout are referencing the same i value. So for each one, i is 8.

You need to create a closure to "capture" the i values.

var createFunc = function(i){
    return function(){
        console.log(animel[i]);
    };
};
for (var i = 0; animel.length > i; i++) {
    setTimeout(createFunc(i), 2000);
}
gen_Eric
  • 223,194
  • 41
  • 299
  • 337
2

There's nothing wrong with the array, the problem is how your closing over the variable i.

By the time the functions execute (2 seconds after the loops completes) i has been incremented beyond the bounds of animel. The easy solution is to provide the current value of i to setTimeout and receive it as a parameter in the function, like this:

for (var i = 0; animel.length > i; i++) {
    setTimeout(function (i) {
        console.log(animel[i]);
    }, 2000, i);
}

If you need to support this syntax on IE < 9, the MDN article provides several polyfill techniques.

p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • Why are you passing `i` to `setTimeout`? What will that do? – gen_Eric Feb 18 '14 at 20:44
  • @RocketHazmat [`window.setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout) accepts parameters to pass to the function when it executes. – p.s.w.g Feb 18 '14 at 20:46
  • Not a solution compatible will browsers though, notably IE < 9. – deceze Feb 18 '14 at 20:47
  • Do browsers actually support this? I've never seen that syntax before. – gen_Eric Feb 18 '14 at 20:47
  • @RocketHazmat It's supported by modern browsers and standardized in [HTML5](http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#timers). – p.s.w.g Feb 18 '14 at 20:55
  • Oh, ok cool. I guess it might need to be polyfilled in older browsers, though? – gen_Eric Feb 18 '14 at 20:59
  • 1
    @RocketHazmat Correct, the MDN article provides several techniques, but they are somewhat complex, and given that there are other solid answers on this question describing backward compatible solutions, I chose not to include them. – p.s.w.g Feb 18 '14 at 21:02
  • I dunno, I like this syntax better! :-D – gen_Eric Feb 18 '14 at 21:04
1

This is a JS enclosure candidate. The value of i used in the setTimeout scope isn't the one you think at the time it is used. To force the actual value of i to be used when the timeout happens, you can use enclosure around it in a way that it will use i as a constant rather than an iterator.

for (var i = 0; i<animel.length; i++) {
    (function(x){
        setTimeout(function() {
            console.log(animel[x]);
        }, 500);
    })(i);
}

DEMO

Frederik.L
  • 5,522
  • 2
  • 29
  • 41
0

You're wrapping it in a setTimeout function which is a non-blocking operation. By the time the loop has completed, i has been incremented to a value which breaks the loop and would be beyond the bounds of the array.

pwnyexpress
  • 1,016
  • 7
  • 14