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?

4
  • 6
    I don't want to edit your post for nothing, but it's animal. The rest of the post is spot on :) Commented Feb 18, 2014 at 20:42
  • 1
    animel[8] will always be undefined, the counter i has already been incremented when the settimeout trigger. Commented Feb 18, 2014 at 20:44
  • 1
    The misspelling of animal in this and all the answers is really bothering me >.< /OCD Commented Feb 18, 2014 at 21:10
  • A variable is a variable dude.... There's nothing called misspelling, only errors ;) Commented Feb 19, 2014 at 6:06

5 Answers 5

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);
Sign up to request clarification or add additional context in comments.

2 Comments

wow... such code... so brackets... much closure...
(I (think) I was (a (Lisp ((((developer)) in a) former) life))).
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);
}

Comments

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.

8 Comments

Why are you passing i to setTimeout? What will that do?
@RocketHazmat window.setTimeout accepts parameters to pass to the function when it executes.
Not a solution compatible will browsers though, notably IE < 9.
Do browsers actually support this? I've never seen that syntax before.
@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.
|
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

Comments

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.