1

This is my code. What I want it to do is write 0, wait one sec, write 1, wait one sec, write 2, wait one sec, etc. Instead it writes 5 5 5 5 5

for(i = 0; i < 5; i++) {
 setTimeout("document.write(i + ' ')", 1000);
}

http://jsfiddle.net/Xb7Eb/

FishBasketGordo
23.2k4 gold badges60 silver badges92 bronze badges
asked Jul 31, 2011 at 17:27
1
  • Do you want a 1s wait before printing 0? Commented Jul 31, 2011 at 18:53

8 Answers 8

8

1) You set all the timeouts to last 1 second at the same time. The loop doesn't wait for the timeout to occur. So you have 5 timeouts that all execute at the same time.

2) When the timeouts execute, the loop is long since complete and i has become 5. So once they execute, they all print "5"

3) document.write() writes somthing onto the page, in the same place it executes. I.e. if you have <script>document.write("xyz")</script> in the middle of a piece of text, it'll write "xyz" in the middle of the text. The timeouts, however, are not necessarily anywhere on the page. They exist only in code.

Here's a solution that's as close to yours as possible: http://jsfiddle.net/rvbtU/1/

var container = document.getElementById("counter");
for(i = 0; i < 5; i++) {
 setTimeout("container.innerHTML += '" + i + " ';", 1000 * i);
}

However, that solution uses setTimeout's ability to evaluate a string as javascript, which is never a good idea.

Here's a solution that uses an anymous function instead: http://jsfiddle.net/YbPVX/1/

var container = document.getElementById("counter");
var writer = function(number) {
 return function() { container.innerHTML += String(number) + " "; };
}
for(i = 0; i < 5; i++) {
 setTimeout(writer(i), 1000 * i);
}

Edit: Forgot to save the 2nd fiddle. Whoops. Fixed now.

answered Jul 31, 2011 at 17:34
Sign up to request clarification or add additional context in comments.

Comments

3

(削除) Most of the answers available are giving bad advice. (削除ここまで)* Specifically, you shouldn't be passing a string to setTimeout anymore (it still works, but it's discouraged), it's no longer 2000, there are better ways to do this.

setTimeout takes a function as the first parameter, and that's what you should do, however there are some issues when calling setTimeout in a loop.

This looks like it should work:

var i;
for ( i = 0; i < 5; i++ )
{
 setTimeout(function(){
 document.write( i + ' ' );
 }, 1000 * (i + 1) );
}

But it doesn't. The issue is that by the time setTimeout executes the function, the loop will have incremented i to 5, so you'll get the same value repeated.

There are a few fixes. If you're willing to risk a with statement, you could try the following:

var i;
for ( i = 0; i < 5; i++ )
{
 with( { i:i } )
 {
 setTimeout(function(){
 document.write( i + ' ' );
 }, 1000 * (i+1) );
 }
}

Note that with is typically discouraged just like passing string values to setTimeout, so I don't really suggest this method of doing things.

The better way is to use a closure:

var i;
for ( i = 0; i < 5; i++ )
{
 (function(i){
 setTimeout(function(){
 document.write( i + ' ' );
 }, 1000 * (i+1) );
 })(i);
}

To explain what's going on, the anonymous function wrapper (function(i){...code...}) executes immediately because it's wrapped in parens and passed i as a value:

(function(i){...code...})(i);

This forces the i variable that document.write uses to be a different one than what's being used in the for loop. You could even change the parameter used in the anonymous function wrapper if the difference gets too confusing:

(function(a){document.write(a+' ')})(i);

* when I started writing this question there were a number of answers describing how to fix the string to work with setTimeout, although they would technically work, they didn't include why they would work (because 'document.write("' + i + ' ");' evaluates i at the time of calling due to string concatenation, versus evaluating i at runtime like the previous version did), and they most certainly didn't mention that it's the bad old way of calling setTimeout.

answered Jul 31, 2011 at 17:46

2 Comments

You're example still shows all five numbers at the same time. You are triggering all five timeouts 1 second after the for loop runs. In order to not trigger them at the same time, you could use i * 1000 as your delay ==> jsfiddle.net/pajtai/FvDTN ( vs how you currently have it ==> jsfiddle.net/pajtai/wU3vB )
@Peter, whoops I had meant to do 1000 * (i+1) in each of them and somehow forgot.
2

try

var i = 1;
function timeout(){
 document.write(i + ' ');
 i++;
 if (i == 5) return;
 setTimeout(timeout, 1000);
}
 timeout();

http://jsfiddle.net/nnJcG/1/

answered Jul 31, 2011 at 17:30

1 Comment

I suggest you change function(){timeout();} to timeout
1

You have a problem with clousures, you can try this:

var timeout = function(){
 var i = 0;
 return function(){ 
 document.write(i+ ' ');
 i++;
 if(i!==5)
 setTimeout(timeout,1000);
 };
}();
setTimeout(timeout,1000);

Here is the example in jsBin http://jsbin.com/uloyuc/edit

answered Jul 31, 2011 at 17:42

Comments

1

First of all, NEVER pass a string to setTimeout. Use a function, it's much cleaner.

Second, you have to "close over" the loop value. I bet this is what you want.

for(var i = 0; i < 5; i++) {
 (function(i) {
 setTimeout(function() {
 document.write(i + ' ')
 }, i * 1000);
 }(i));
}

See more about you a self executing function to close over a loop value here http://www.mennovanslooten.nl/blog/post/62


And just cause I love it, here is the equivalent in CoffeeScript whihc has the do keyword to help out with just this case.

for i in [0..4]
 do (i) ->
 setTimeout ->
 document.write "#{ i } "
 , i * 1000
answered Jul 31, 2011 at 17:45

1 Comment

To try out coffescript go here ==> jashkenas.github.com/coffee-script . Click on "Try Coffeescript" at the top, paste in Squeegy's code, and hit Run.
1

You can also work with setInterval and clearInterval:

var i = 0;
var f = setInterval(function() {
 if(i == 4) clearInterval(f);
 document.write(++i + ' ');
}, 1000);

I think this code is very readable.

answered Jul 31, 2011 at 17:39

1 Comment

while i don't disagree that the code is readable, I would move the ++i call to the line before document.write. In this case the line is short and the ++i is visible, but for more complicated code, it's really easy to lose an increment operation in the middle of something else. It's better IMHO to have one operation per line.
1

You could try like this:

var tick_limit = 5; // Or any number you wish representing the number of ticks
var counter = 0; // Or any number you wish 
var timer_interval = 1000; // Interval for the counter
var timer;
function timerTick()
{
 if(counter < tick_limit)
 {
 // Execute code and increase current count
 document.body.innerHTML+=(counter + ' '); // Append the counter value to the body of the HTML page
 counter++;
 timer = setTimeout(timerTick,timer_interval);
 }
 else
 {
 // Reset everything
 clearTimeout(timer);
 counter = 0;
 }
}
function startCounter()
{
 clearTimeout(timer); // Stop current timer
 timer = setTimeout(timerTick,timer_interval); // Start timer with any interval you wish
}
...
// Start timer when required
startCounter();
...

This way, calling the startCounter a number of times will result in a single timer executing the code

answered Jul 31, 2011 at 17:49

1 Comment

Since you've started putting the settings into variables, you might as well make the interval a variable, so everything can be adjusted by editing only the top of the script. ---------- Also, this only only shows one number. I think you want setInterval and not setTimeout.
0

You're triggering five timeouts at the same time.

I like Pindatjuh's answer, but here's another fun way to do it.

This way starts the next timeout when the previous one is finished:

// Wrap everything in a self executing anonymous function so we don't pollute
// the global namespace.
//
// Note: Always use "var" statments or you will pollute the global namespace!
// For example "for(i = 0; i < 5; i++)" will pollute the global namespace
// unless you have "var i; for(i = 0; i < 5; i++)" or 
// "for(var i = 0; i < 5; i++)" & all of that is not in the global namespace.
// 
(function() {
 // "i" will be available within doThis()
 // you could also pass "i" as an argument
 var i = 0,
 doThis = function() {
 // setTimeout can take an anonymous function
 // or a regular function. This is better than
 // eval-ing a string.
 setTimeout(function() {
 document.write(i + ' ');
 ++i;
 // Do the function again if necessary
 if (i < 5) doThis();
 }, 1000);
 }
 // Let's begin! 
 doThis();
})();

Working Example

answered Jul 31, 2011 at 17:48

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.