Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why does javascript setTimeout() not work in a loop? [duplicate]

Consider the following code:

<!DOCTYPE html>
<html>
<head>
<script>
function timedText()
{
  var x=document.getElementById('txt');
  var t = new Array();
  t[1] = setTimeout( function(){x.value="2 seconds"}, 2000 );
  t[2] = setTimeout( function(){x.value="4 seconds"}, 4000 );
  t[3] = setTimeout( function(){x.value="6 seconds"}, 6000 );
}
function timedTextArr()
{
  var x=document.getElementById('txt');
  var t = new Array();
  for( var i = 0 ; i < 3 ; i++ ) {
    t[i] = setTimeout( function(){x.value=i*2+" seconds"}, i*2000 );
  }
}
</script>
</head>
<body>
<form>
<input type="text" id="txt" />
<input type="button" value="Display timed text!" onclick="timedText()" />
<input type="button" value="Display timed text Arr!" onclick="timedTextArr()" />
</form>
<p>Click on the button above. The input field will tell you when two, four, and six seconds have passed.</p>
</body>
</html>

Function timedText() works, but timedTextArr() doesn't. Both functions assign return values from setTimeout() to array elements. But in the for() loop, only the last timer works... and it works three times.

Is this a bug?

like image 544
user2624632 Avatar asked Dec 04 '13 19:12

user2624632


2 Answers

This is not a bug, have a look at what closures are in Javascript.

Basically in your for loop the function

function(){x.value=i*2+" seconds"}

only "sees" one instance of the i variable.

So once the loop is over, i is equal to 3, so it is 3 for all functions.

You need to wrap the call in another anonymous function to create a scope, like this:

t[i] = setTimeout( (function(i){ return function(){x.value=i*2+" seconds"}})(i), i*2000 );

The outer function will create a new scope, and inside it i will be equal to the value of i in the loop and stay like this. You can try it out there: http://jsfiddle.net/6b68E/

like image 154
djfm Avatar answered Nov 11 '22 15:11

djfm


The i in your function refers to the i from the loop, which is 6 by the time any of the timeouts fire. You need to add a closure/scope:

  for( var i = 0 ; i < 3 ; i++ ) {
    (function(){    // create a closure (new scope)
      var _i = i;   // make a local copy of `i` from the outer scope
      t[i] = setTimeout( function(){x.value=_i*2+" seconds"}, i*2000 );
    })();
  }
like image 30
svidgen Avatar answered Nov 11 '22 17:11

svidgen