3

I have a for loop in which I need to delay each repetition for animation purposes. If I remove the setTimeOut function, the following code properly cycles and the variable correctly increments through the loop, following which, the bottom line executes. But with the setTimeoout function, the bottom line executes first, and then the for loop executes 7 times (should be 6), telling me each time that x = 6. Clearly I'm doing something wrong. Any ideas?

for ( x = 0; x <= 5; x++) {
    setTimeout(function() {
        alert("For loop iteration #" + x);
    }, 500 * x);
}
alert("Code to be executed after completed for loop");
3

3 Answers 3

5

You need a closure to save current x value in closure context.

 for (var x = 0; x <= 5; x++) {
    (function(x) {
        setTimeout(function(){
            alert("For loop iteration #" + x);
            if (x == 5) {
                setTimeout(function(){
                    alert("Code to be executed after completed for loop");
                });
            }
        }, 5 * x);

    })(x);
}
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks. This fixes the problem with the alert inside of the loop, though the line after the loop is still executing first, whereas I need it to not execute until the loop is finished.
Try new version from my answer.
Why the setTimeout without any delay parameter instead of a plain call?
It's one of the ways to set callback with "zero" delay. w3.org/html/wg/drafts/html/master/webappapis.html#timers The most common problem, is that setTimeout as far as I know formally not in ecma script spec at this time.
3

This is a common conceptual mistake.

  1. Javascript is non-blocking
  2. A reference to the variable is passed, not the actual value

You have to keep in mind, that the variable x is dynamic. A reference to x is passed to the alert("For loop iteration #" + x); not the value. Thus when the alert finally is executed x will have the value which it has at the point of execution not at the point where the setTimeout was initiated!

Essentially it goes like this:
Your loop is processed, creating 6 timeouts and immediately after that will show your alert("Code to be executed after completed for loop");. Then after the some time, your timeouts get executed which then will all show the variable x in it's state after the loop is finished - 6.

You need a closure so that the value of x gets handed over to the alert, not the reference to the variable x itself.

for (var x = 0; x <= 5; x++) {
    (function(z) {
        setTimeout(function() {
            alert("For loop iteration #" + z);
        }, 5 * z);
    })(x);
}

EDIT:

To tackle your second problem, you need to use a callback function. A CB function is the logical continuation of your code, but shall not be executed immediately but needs to be stalled until a certain point (your last alert has occured). You would implement it like this:

for (var x = 0; x <= 5; x++) {
    (function(z) {
        setTimeout(function() {
            alert("For loop iteration #" + z);
            if (z===5){ continue_code() }
        }, 5 * z);
    })(x);
}

function continue_code(){
    alert("Code to be executed after completed for loop");
    // Here comes all your code
    // which has to wait for the timeouts from your for loop
}

In the last setTimeout you call the function which continues the execution of your code.

7 Comments

In Javascript variables not passed by reference! It's passed to function by sharing!
@Silver_Clash I did not talk about how parameters get handed over between functions but expressions inside the scope of a function. It's a reference which is passed to the alert and not the actual value. This is typically called a call by reference and not by value.
Alert just use variable object from context, where it called. If we don't use closure, than alert use the same context as the for loop (alert just use variable, and variable don't declare in setTimeout function context, and it's get variable from scope chain, there is no passing variable!!!)! Talk about passing a variable in this case there is no point, because we do not change the context.
Good to know, thanks. Your code above fixes the problem with the alert inside of the loop, though the line after the loop is still executing first, whereas I need it to not execute until the loop is finished.
|
1

x is a global variable. You've incremented it to 6 by the time the first alert happens. If you don't want that to happen, use something like this instead, which increments inside the function that is called every 500ms:

var x = 0;
var interval = setInterval(function() {
    alert("Loop iteration #" + x++);
    if(x==5) {
        clearInterval(interval);
        alert("Code to be executed after completing loop");
    }
}, 500);

6 Comments

Why is it worth downrating this?... I realise I changed the for and setTimeout into setInterval, but that's a suggestion and it works just as well.
I did not downvote, but a possible explanation why it got downvoted. 1) it does not matter if the variable is global or not. 2) While the result is still the same, your code works completely different like the OPs code. 3) You create additional global (=evil) variables where a simple closure would have fixed the problem.
This seems to work well, only the last line still executes first for me, and I need it to execute last.
I overlooked that the last line is supposed to be called last. Fixed that. As for the other comment: thanks for the feedback, but I do not agree with all of them, as I believe using an interval is a more suitable solution in this case, compared to timeouts and a closure :) (I do admit that I was assuming the code fragment is inside some other function, giving the variables a limited scope)
This is definitely the easiest solution. Thanks.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.