1
var a=0;
setTimeout (function () { animatedDraw(context, 20+32*level[0],20*0, textArray[0]); }, timeArray[0]);
setTimeout (function () { animatedDraw(context, 20+32*level[1],20*1, textArray[1]); }, timeArray[1]);
setTimeout (function () { animatedDraw(context, 20+32*level[2],20*2, textArray[2]); }, timeArray[2]);
setTimeout (function () { animatedDraw(context, 20+32*level[3],20*3, textArray[3]); }, timeArray[3]);
setTimeout (function () { animatedDraw(context, 20+32*level[4],20*4, textArray[4]); }, timeArray[4]);
setTimeout (function () { animatedDraw(context, 20+32*level[5],20*5, textArray[5]); }, timeArray[5]);

for (a=0; a<6; a++)
    setTimeout (function () { animatedDraw(context, 20+32*level[a],20*0, textArray[a]); }, timeArray[a]);

The first part of my code is the part that works. The second part does not show up. I am drawing in a canvas (HTML 5), but when I popped six alert boxes, the alert boxed showed. Am I doing something very stupid wrong?

Thanks in advance

3 Answers 3

12

The reason is that the functions you're feeding into setTimeout are closures, and closures have an enduring reference to the variables they close over, not a copy of their values as of when the closure was created. Consequently, all of those functions will try to use the same value of a, the value it has after the loop is complete (e.g. 6) and so they'll fail.

The answer is to have the functions close over some other data instead that won't change. The usual way to do that is to have a factory function that creates and returns the actual functions you want, having them close over the argument you feed into the factory function (which won't change) rather than your loop variable. E.g.:

for (a=0; a<6; a++) {
    setTimeout(makeTimerFunction(a), timeArray[a]);
}

function makeTimerFunction(index) {
    return function () {
        animatedDraw(context, 20+32*level[index],20*0, textArray[index]);
    };
}

As you can see, now the functions being created by makeTimerFunction are closing over index rather than a (and also over context, level, and textArray; you'd pass those in as well if they change).

More on closures: Closures are not complicated

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

7 Comments

I'll accept your answer when I can. Thanks, this was exactly my problem! When I reduced my loop by one, the last statement showed.
@Michael: No, that wouldn't help, because if it's within the function it's still using a as of when the function runs. You need a factory or similar; I've added an example of one.
@Hidde: Glad that helped. I added an example of how you can deal with it.
@Hidde: Also, I noticed in the loop you have "level[a]20*0" when I think you want to reference the loop variable again "level[a]20*a"
Yeah, I was experimenting a lot with all the 'a's, and I forgot that one. Thanks though, I already had it repaired in my file :)
|
2

This is the very common Javascript closure problem.

Your variable a in the loop continues to change during the loop, so the value used in the callbacks will be the last value it had, not the value it happened to have each time through the loop.

The easiest fix is this:

function timer_setup(a) {
    setTimeout(function () {
        animatedDraw(context, 20+32*level[a],20*0, textArray[a]);
    }, timeArray[a]);
};

for (a=0; a<6; a++) {
    timer_setup(a);
}

Comments

0

Try this...

for (a=0; a<6; a++)
{
    (function(index)
    {
        setTimeout (function () { animatedDraw(context, 20+32*level[index],20*0, textArray[index]); }, timeArray[index]);
    })(a)
}

2 Comments

That will create and throw away the factory function on every loop, which is wasteful. Probably harmless, but wasteful.
I agree, and I think your solution was much better.

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.