This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
I have this script:
for (var i = 1; i <= 2; i++) {
setTimeout(function() { alert(i) }, 100);
}
But 3 is alerted both times, instead of 1 then 2.
Is there a way to pass i, without writing the function as a string?
You have to arrange for a distinct copy of "i" to be present for each of the timeout functions.
function doSetTimeout(i) {
setTimeout(function() {
alert(i);
}, 100);
}
for (var i = 1; i <= 2; ++i)
doSetTimeout(i);
If you don't do something like this (and there are other variations on this same idea), then each of the timer handler functions will share the same variable "i". When the loop is finished, what's the value of "i"? It's 3! By using an intermediating function, a copy of the value of the variable is made. Since the timeout handler is created in the context of that copy, it has its own private "i" to use.
Edit:
There have been a couple of comments over time in which some confusion was evident over the fact that setting up a few timeouts causes the handlers to all fire at the same time. It's important to understand that the process of setting up the timer — the calls to setTimeout() — take almost no time at all. That is, telling the system, "Please call this function after 1000 milliseconds" will return almost immediately, as the process of installing the timeout request in the timer queue is very fast.
Thus, if a succession of timeout requests is made, as is the case in the code in the OP and in my answer, and the time delay value is the same for each one, then once that amount of time has elapsed all the timer handlers will be called one after another in rapid succession.
If what you need is for the handlers to be called at intervals, you can either use setInterval(), which is called exactly like setTimeout() but which will fire more than once after repeated delays of the requested amount, or instead you can establish the timeouts and multiply the time value by your iteration counter. That is, to modify my example code:
function doScaledTimeout(i) {
setTimeout(function() {
alert(I);
}, i * 5000);
}
(With a 100 millisecond timeout, the effect won't be very obvious, so I bumped the number up to 5000.) The value of i is multiplied by the base delay value, so calling that 5 times in a loop will result in delays of 5 seconds, 10 seconds, 15 seconds, 20 seconds, and 25 seconds.
Update
Here in 2018, there is a simpler alternative. With the new ability to declare variables in scopes more narrow than functions, the original code would work if so modified:
for (let i = 1; i <= 2; i++) {
setTimeout(function() {
alert(i)
}, 100);
}
The let declaration, unlike var, will itself cause there to be a distinct i for each iteration of the loop.
You can use an immediately-invoked function expression (IIFE) to create a closure around setTimeout:
for (var i = 1; i <= 3; i++) {
(function(index) {
setTimeout(function() { alert(index); }, i * 1000);
})(i);
}
This's Because!
The timeout function
callbacks are all running well after the completion of the loop. In fact,
as timers go, even if it was setTimeout(.., 0) on each iteration, all
those function callbacks would still run strictly after the completion
of the loop, that's why 3 was reflected!
all two of those functions, though they are defined
separately in each loop iteration, are closed over the same shared global
scope, which has, in fact, only one i in it.
the Solution's declaring a single scope for each iteration by using a self-function executed(anonymous one or better IIFE) and having a copy of i in it, like this:
for (var i = 1; i <= 2; i++) {
(function(){
var j = i;
setTimeout(function() { console.log(j) }, 100);
})();
}
the cleaner one would be
for (var i = 1; i <= 2; i++) {
(function(i){
setTimeout(function() { console.log(i) }, 100);
})(i);
}
The use of an IIFE(self-executed function) inside each iteration created a new scope for each
iteration, which gave our timeout function callbacks the opportunity
to close over a new scope for each iteration, one which had a variable
with the right per-iteration value in it for us to access.
The function argument to setTimeout is closing over the loop variable. The loop finishes before the first timeout and displays the current value of i, which is 3.
Because JavaScript variables only have function scope, the solution is to pass the loop variable to a function that sets the timeout. You can declare and call such a function like this:
for (var i = 1; i <= 2; i++) {
(function (x) {
setTimeout(function () { alert(x); }, 100);
})(i);
}
You can use the extra arguments to setTimeout to pass parameters to the callback function.
for (var i = 1; i <= 2; i++) {
setTimeout(function(j) { alert(j) }, 100, i);
}
Note: This doesn't work on IE9 and below browsers.
ANSWER?
I'm using it for an animation for adding items to a cart - a cart icon floats to the cart area from the product "add" button, when clicked:
function addCartItem(opts) {
for (var i=0; i<opts.qty; i++) {
setTimeout(function() {
console.log('ADDED ONE!');
}, 1000*i);
}
};
NOTE the duration is in unit times n epocs.
So starting at the the click moment, the animations start epoc (of EACH animation) is the product of each one-second-unit multiplied by the number of items.
epoc: https://en.wikipedia.org/wiki/Epoch_(reference_date)
Hope this helps!
You could use bind method
for (var i = 1, j = 1; i <= 3; i++, j++) {
setTimeout(function() {
alert(this);
}.bind(i), j * 100);
}
Well, another working solution based on Cody's answer but a little more general can be something like this:
function timedAlert(msg, timing){
setTimeout(function(){
alert(msg);
}, timing);
}
function yourFunction(time, counter){
for (var i = 1; i <= counter; i++) {
var msg = i, timing = i * time * 1000; //this is in seconds
timedAlert (msg, timing);
};
}
yourFunction(timeInSeconds, counter); // well here are the values of your choice.
I had the same problem once this is how I solved it.
Suppose I want 12 delays with an interval of 2 secs
function animate(i){
myVar=setTimeout(function(){
alert(i);
if(i==12){
clearTimeout(myVar);
return;
}
animate(i+1)
},2000)
}
var i=1; //i is the start point 1 to 12 that is
animate(i); //1,2,3,4..12 will be alerted with 2 sec delay
the real solution is here, but you need to be familiar with PHP programing language.
you must mix PHP and JAVASCRIPT orders in order to reach to your purpose.
pay attention to this :
<?php
for($i=1;$i<=3;$i++){
echo "<script language='javascript' >
setTimeout(function(){alert('".$i."');},3000);
</script>";
}
?>
It exactly does what you want, but be careful about how to make ralation between
PHP variables and JAVASCRIPT ones.
Related
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
I have this script:
for (var i = 1; i <= 2; i++) {
setTimeout(function() { alert(i) }, 100);
}
But 3 is alerted both times, instead of 1 then 2.
Is there a way to pass i, without writing the function as a string?
You have to arrange for a distinct copy of "i" to be present for each of the timeout functions.
function doSetTimeout(i) {
setTimeout(function() {
alert(i);
}, 100);
}
for (var i = 1; i <= 2; ++i)
doSetTimeout(i);
If you don't do something like this (and there are other variations on this same idea), then each of the timer handler functions will share the same variable "i". When the loop is finished, what's the value of "i"? It's 3! By using an intermediating function, a copy of the value of the variable is made. Since the timeout handler is created in the context of that copy, it has its own private "i" to use.
Edit:
There have been a couple of comments over time in which some confusion was evident over the fact that setting up a few timeouts causes the handlers to all fire at the same time. It's important to understand that the process of setting up the timer — the calls to setTimeout() — take almost no time at all. That is, telling the system, "Please call this function after 1000 milliseconds" will return almost immediately, as the process of installing the timeout request in the timer queue is very fast.
Thus, if a succession of timeout requests is made, as is the case in the code in the OP and in my answer, and the time delay value is the same for each one, then once that amount of time has elapsed all the timer handlers will be called one after another in rapid succession.
If what you need is for the handlers to be called at intervals, you can either use setInterval(), which is called exactly like setTimeout() but which will fire more than once after repeated delays of the requested amount, or instead you can establish the timeouts and multiply the time value by your iteration counter. That is, to modify my example code:
function doScaledTimeout(i) {
setTimeout(function() {
alert(I);
}, i * 5000);
}
(With a 100 millisecond timeout, the effect won't be very obvious, so I bumped the number up to 5000.) The value of i is multiplied by the base delay value, so calling that 5 times in a loop will result in delays of 5 seconds, 10 seconds, 15 seconds, 20 seconds, and 25 seconds.
Update
Here in 2018, there is a simpler alternative. With the new ability to declare variables in scopes more narrow than functions, the original code would work if so modified:
for (let i = 1; i <= 2; i++) {
setTimeout(function() {
alert(i)
}, 100);
}
The let declaration, unlike var, will itself cause there to be a distinct i for each iteration of the loop.
You can use an immediately-invoked function expression (IIFE) to create a closure around setTimeout:
for (var i = 1; i <= 3; i++) {
(function(index) {
setTimeout(function() { alert(index); }, i * 1000);
})(i);
}
This's Because!
The timeout function
callbacks are all running well after the completion of the loop. In fact,
as timers go, even if it was setTimeout(.., 0) on each iteration, all
those function callbacks would still run strictly after the completion
of the loop, that's why 3 was reflected!
all two of those functions, though they are defined
separately in each loop iteration, are closed over the same shared global
scope, which has, in fact, only one i in it.
the Solution's declaring a single scope for each iteration by using a self-function executed(anonymous one or better IIFE) and having a copy of i in it, like this:
for (var i = 1; i <= 2; i++) {
(function(){
var j = i;
setTimeout(function() { console.log(j) }, 100);
})();
}
the cleaner one would be
for (var i = 1; i <= 2; i++) {
(function(i){
setTimeout(function() { console.log(i) }, 100);
})(i);
}
The use of an IIFE(self-executed function) inside each iteration created a new scope for each
iteration, which gave our timeout function callbacks the opportunity
to close over a new scope for each iteration, one which had a variable
with the right per-iteration value in it for us to access.
The function argument to setTimeout is closing over the loop variable. The loop finishes before the first timeout and displays the current value of i, which is 3.
Because JavaScript variables only have function scope, the solution is to pass the loop variable to a function that sets the timeout. You can declare and call such a function like this:
for (var i = 1; i <= 2; i++) {
(function (x) {
setTimeout(function () { alert(x); }, 100);
})(i);
}
You can use the extra arguments to setTimeout to pass parameters to the callback function.
for (var i = 1; i <= 2; i++) {
setTimeout(function(j) { alert(j) }, 100, i);
}
Note: This doesn't work on IE9 and below browsers.
ANSWER?
I'm using it for an animation for adding items to a cart - a cart icon floats to the cart area from the product "add" button, when clicked:
function addCartItem(opts) {
for (var i=0; i<opts.qty; i++) {
setTimeout(function() {
console.log('ADDED ONE!');
}, 1000*i);
}
};
NOTE the duration is in unit times n epocs.
So starting at the the click moment, the animations start epoc (of EACH animation) is the product of each one-second-unit multiplied by the number of items.
epoc: https://en.wikipedia.org/wiki/Epoch_(reference_date)
Hope this helps!
You could use bind method
for (var i = 1, j = 1; i <= 3; i++, j++) {
setTimeout(function() {
alert(this);
}.bind(i), j * 100);
}
Well, another working solution based on Cody's answer but a little more general can be something like this:
function timedAlert(msg, timing){
setTimeout(function(){
alert(msg);
}, timing);
}
function yourFunction(time, counter){
for (var i = 1; i <= counter; i++) {
var msg = i, timing = i * time * 1000; //this is in seconds
timedAlert (msg, timing);
};
}
yourFunction(timeInSeconds, counter); // well here are the values of your choice.
I had the same problem once this is how I solved it.
Suppose I want 12 delays with an interval of 2 secs
function animate(i){
myVar=setTimeout(function(){
alert(i);
if(i==12){
clearTimeout(myVar);
return;
}
animate(i+1)
},2000)
}
var i=1; //i is the start point 1 to 12 that is
animate(i); //1,2,3,4..12 will be alerted with 2 sec delay
the real solution is here, but you need to be familiar with PHP programing language.
you must mix PHP and JAVASCRIPT orders in order to reach to your purpose.
pay attention to this :
<?php
for($i=1;$i<=3;$i++){
echo "<script language='javascript' >
setTimeout(function(){alert('".$i."');},3000);
</script>";
}
?>
It exactly does what you want, but be careful about how to make ralation between
PHP variables and JAVASCRIPT ones.
I've found some example in a tutorial (said it was the canonical example)
for (var i=1; i<=5 ; i++) {
setTimeout(function() {
console.log("i: " + i);
}, i*1000);
}
Now, I understand that, closure passes in the current scope in to the function, and I assume that it should output 1,2,3,4,5. But instead, it prints number 6 five times.
I ran it in the chrome debugger, and first it goes through the loop without going in to the function while doing the increment of the i value and only after that, it goes in to the inner function and execute it 5 times.
I'm not sure why its doing that, I know, the current scoped is passed in to the function because of closure, but why does it not execute each time the loop iterate?
By the time the timeout runs, the for loop has finished, and i is 6, that's why you're getting the output you see. You need to capture i during the loop:
for (var i=1; i<=5 ; i++) {
(function(innerI) {
setTimeout(function() {
console.log("i: " + innerI);
}, innerI*1000);
})(i);
}
This creates an inner function with it's own parameter (innerI), that gets invoked immediately and so captures the value of i for use within the timeout.
If you didn't want the complex-looking IIFE as explained in James' answer, you can also separate out the function using bind:
function count(i) {
console.log("i: " + i);
}
for (var i = 1; i <= 5; i++) {
setTimeout(count.bind(this, i), i * 1000);
}
Thank you for you help,
I found out another solution and it was a minor change.
On the top of the page I turned on the strict mode and also in the for loop, Instead of var, I used the "let" keyword.
I have this code:
for (i = 0; i < 3; i++) {
var interval = setInterval(function(){
alert(i);
}, 2000);
}
What I would like to achieve is to have an alert every 2 sec displaying first 0, then 1 and lastly 2.
Instead I have to wait for quite long before I have 3 alerts all displaying the number 3. Where is my code wrong?
Well, there is (yet again) more than one solution to this problem. But, lets first talk why your code doesn't work properly.
Your code:
for (i = 0; i < 3; i++) {
var interval = setInterval(function(){
alert(i);
}, 2000);
}
..basically means that it will assign three setInterval calls to be executed after 2 seconds as fast as the for loop is placing them to the queue. So basically, all your calls runs really fast after 2 seconds, only few milliseconds or less between them. Moreover, setInterval means that it will be called as long as clearInterval is called to the variable it is assigned with. In other words, your code never stops executing the alert, because you are never calling clearInterval. Finally, your alert(i) will always display value of 3, because it is the last value of i when execution moves away from the for loop.
To improve your code, you could remove the for loop entirely and just let setInterval run as long as the value of i is alerted three times; At that point, you just call clearInterval to the value which has handle to setInterval and the job is finished.
Working code:
// value to output
var i = 0,
// starting setInterval and assigning its handle to variable interval,
// it is used to clear the interval when execution should be finished
interval = setInterval(function () {
// alert value of i and increase it by 1
alert(i++);
// if i is equals to 3, no more calls
if(i === 3) {
// clear the interval so method won't be called in the future
clearInterval(interval);
}
}, 2000);
JS FIDDLE EXAMPLE
Cheers, hopefully you learnt something new today.
Without forloop:
var number = 0;
var interval = setInterval(function(){
alert(number);
number++;
if(number === 3) {
clearInterval(interval);
}
}, 2000);
JSFIDDLE
1.1 Without for loop + without initial delay (demo):
var i = 0;
var showAlert = function(){
alert(i);
i++;
if(i < 3){
setTimeout(function(){
showAlert();
}, 2000);
}
};
showAlert();
1.2 Without for loop + with initial delay (demo):
var i = 0;
var showAlert = function(){
if(i < 3){
setTimeout(function(){
alert(i);
i++;
showAlert();
}, 2000);
}
};
showAlert();
2.1 With for loop + without initial delay (demo):
function setAlert(k){
setTimeout(function(){
alert(k);
},k * 2000);
}
for(var i = 0; i < 3; i++) {
setAlert(i);
}
2.2 With for loop + with initial delay (demo):
function setAlert(k){
setTimeout(function(){
alert(k);
},(k + 1) * 2000);
}
for(var i = 0; i < 3; i++) {
setAlert(i);
}
First of all, I would go with setTimout, you know that you want 3 alerts.
Second problem is a bit stranger. You are calling async function, setTimeout/setInterval and referring to the original i of the for loop inside of the setTimeout callback. That will not work because at the time of the timeout invocation the for loop has already finished and i var will be 3. One solution is to wrapp the async function in a self invoking anonymous function with params that you need inside async function. In our case we call it with i.
Solution:
for (i = 0; i < 3; i++) {
(function(i) {
setTimeout(function(){
alert(i);
}, 2000 * i);
})(i);
}
JS fiddle
This question already has answers here:
setTimeout in for-loop does not print consecutive values [duplicate]
(10 answers)
Closed 8 years ago.
i'm writing a code and i've stuck with setTimeout function. I would like to get animation effect. Display elements of array with delay. Code is done.
for (var i=0;i <= array.length-1;i++) {
(function(el) {
setTimeout(function(){
document.getElementById('Result').innerHTML += Math.floor(el);
console.log(Math.floor(el));
}, 3000*(i+1));
})(array[i]);
I had problem with delay when i use for (var i=array.length-1; i>=0;i--) Why? (The idea of this code is display items array form the last to the first)
for (var i=0;i <= array.length-1;i++) {
(function(el) {
setTimeout(function(){
Give now the same resultat as: for (var i=array.length-1; i>=0;i--) {
console.log(array[i]+'!')
The problem here is about closures. A closure is a type of anonymous function used to remember the arguments for using them later in asynchronous code (e.g. in functions called by setTimeout).
If you write something like:
setTimeout(function(foo){...}(value), 3000)
the function gets called before calling setTimeout. You have to use a closure to do this:
(function(foo){
setTimeout(function() {
... do something with foo ...
}, 3000);
})(value_of_foo);
This code will remember the value of foo creating a setTimeout with a function that uses it.
Another problem is that you have to increase the setTimeout time to create a delay, otherwise the for will create a bunch of setTimeout(..., 3000) that will be executed all at once. So what you will need to do to your code is something like the following:
for (var i = 0; i <= array.length; i++) {
(function(el) {
setTimeout(function(){
document.getElementById('Result').innerHTML += Math.floor(el)
}, 3000 * i);
})(array[i]);
}
Timeouts don't execute until after your main function has finished, this means that by the time your timeout function executes per loop, your i variable will be at its final value (in this case = 0). Your function declaration is also incorrect as the timeout function does not pass in those parameters for you. To do this, you need to wrap your timeout inside of another function call that takes in the parameters for the current loop, try something like this instead...
for (var i=array.length-1; i>=0;i--) {
function(array, i) {
setTimeout(function() {
document.getElementById('Result').innerHTML += Math.floor(array[i]);
}, (i+1) * 3000);
}(array, i);
}
First of all you are immediately calling the function and its result is assigned as the callback of the timeout. (this is wrong as the first argument must be a function)
Then, you have some syntax errors with missing closing } and )..
Try
for (var i=array.length-1; i>=0;i--) {
function(array,i){
setTimeout(function(){
document.getElementById('Result').innerHTML += Math.floor(array[i]);
}, 3000*i);
}(array,i);
}
I used 3000*i so that each timeout is 3 seconds after the other
I'd like to add a class to a series of spans using setTimeout() such that the class is added in cascading fashion, creating a visual progression rather than having them all set at once. I've tried so many different ways.
Here's a codepen...
http://codepen.io/geirman/pen/nDhpd
The codepen tries to mimic a working example I've found here...
https://developers.google.com/maps/documentation/javascript/examples/marker-animations-iteration
The problem is that I can't seem to delay the addClass successively, so it happens all at once. Here's the current code.
/* The Problem Code
********************/
var span$ = $("span");
var index = 0;
var factor = 500;
function colorSpans(){
for(var i = 0; i < span$.length; i++){
setTimeout(function(){
animate();
}, factor*index);
}
index = 0;
}
function animate(){
span$[index++].className = "lg";
}
colorSpans();
One more thing, I'd love to do this sans jQuery, but will accept a jQuery solution as well.
In your current code, it looks like the very first call to animate will execute immediately, after which index will be 1. But the loop is likely to finish executing before the second timeout-handler is called (i.e., the loop won't take 500ms to execute). So this means that the remaining spans will appear instantaneously since the value of index will not be updated.
Something like this should work:
function colorSpans() {
for(var i = 0; i < span$.length; i++) {
(function(i) {
setTimeout(function() {
animate(i);
}, factor * i);
})(i); //force a new scope so that the "i" passed to animate()
//is the value of "i" at that current iteration and not
//the value of "i" when the loop is done executing.
}
}
function animate(i) {
span$[i].className = "lg";
}
The above code is also preferable because you're not using a global variable to maintain state between colorSpans and animate (i.e., we're not using index).
But you can make additional improvements by using setInterval instead:
var i = 0;
var intervalId = setInterval(function() {
if(i < span$.length) {
animate(i);
i++;
} else {
clearInterval(intervalId);
}
}, 500)
I think this is cleaner than the setTimeout approach.
Check out the updated codepens:
setTimeout approach
setInterval approach