This question already has answers here:
Closed 6 years ago.
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?
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/
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 );
})();
}
The reason you are getting the same results is setTimeout
is asynchronous. Which means it doesn't run until the rest of the script is finished. Then once it runs the value of i
is set to equal 3 so all the functions run and all they see is that i = 3.
The main issue is that due to the asynchronous nature of timer functions, the loop is done running before the first timeout is called, and therefore i
is set to 2 by the time the first timeout runs, and stays the same for the other two timeouts
To overcome this, you should consider refactoring the code to use an interval instead, which allows you to change the value of i
in sync with the closures:
var i=1;
var handle = setInterval( function() {
x.value = (i*2) + "seconds";
i++;
if (i>3) clearInterval(handle);
}, 2000 );
Other than that the loop is running from 0 to 2, instead of 1 to 3, like in timedText()