I'm trying to set five staggered function calls (happening one second apart). That part works fine. What doesn't work is, I can't pass values 0 through 4 into the callback function. It just passes '5' each time. I can't seem to figure out why and how to fix it.
Code:
function callback(num)
{
console.log(num);
}
for (var i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000)
setTimeout(function() { callback(i); }, loadDelay);
Result:
5
5
5
5
5
Desired result:
0
1
2
3
4
That's because you create a closure. So the function you pass to setTimeout share the same i instances. In the browser that supports the standards (not IE) you could have:
setTimeout(callback, loadDelay, i);
See:
http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#timers
Otherwise you have to actually bind the argument to the function:
setTimeout(callback.bind(undefined, i), loadDelay);
See:
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
If the browser doesn't support ES5 bind method, you can either implement the shim present in the link above, or manually doing something like:
setTimeout(function(index){
return function() { callback(index) }
}(i), loadDelay);
But I would say it's more readable using bind and it's worthy to implement the shim. You can actually use this: https://github.com/kriskowal/es5-shim
To add es5 capabilities (where is possible) in the browser that don't support es5 natively.
Use a lambda / function expression to capture the current value. For example
for (var i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000) {
var doCall = function (j) {
setTimeout(function() { callback(j); }, loadDelay);
}
doCall(i);
}
The problem here is that there is only 1 i value for all iterations of the loop. Variables in javascript have function scope even though you can declare them inside of a block. This means i is alive for the entire function.
To illustrate the problem consider the below code executes exactly the same as your sample
var i;
for (i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000) {
...
}
My solution works because it introduces a new function and hence a new variable lifetime for j. This saves the current value of i in the function for use in the setTimeout callback
You needed a closure in order to pass i due to variable scoping. Check out this article, and this one as well for some good information on closures.
Live Demo
function callback(num)
{
console.log(num);
}
for (var i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000)
setTimeout((function(num){return function(){
callback(num);
}
})(i), loadDelay);
setTimeout creates some odd scoping problems. Frame.js was designed to resolve some of this kind of confusion, this also works [updated]:
function callback(num) {
console.log(num);
}
for (var i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000) {
Frame(function(next, i){
setTimeout(function() { callback(i); }, loadDelay);
next();
}, i);
}
Frame.init();
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.
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.
does anyone know why the output of this code is 5 times 5?
for (var i = 0; i < 5; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}
The for statement never gets to 5 :S
You need to make a closure in order to preserve the value of the i variable. Otherwise, the i variable will be the value of the last iteration when the setTimeout executes
for (var i = 0; i < 5; i++) {
(function(i){
setTimeout(function() { console.log(i); }, i * 1000 );
})(i);
}
Take a look into reading about IIFEs
Just modify a little your code, and it works:
for (var i = 0; i < 5; i++) {
setTimeout(function(x) { console.log(x); }, i * 1000, i);
}
setTimeout
Classic example of closure:
Closures are functions that refer to independent (free) variables. In
other words, the function defined in the closure 'remembers' the
environment in which it was created.
Your inner function has access to the environment (in this example with the outer variable i). After the loop has ended the value of i is 5 and your function use this value. of course if you use console.log(i) after the loop will end.
Try this article - Closure - MDN
Just can't seem to figure this out, how to prevent the outer loop continuing until the inner loop has executed using setTimeout:
function $(s) { return document.getElementById(s); }
var i = 0;
for (i=0; i<6; i++){
$('t'+i).className = 'show';
cl('t'+i);
for (var j=0; j<50; j++){
cl('b'+i+'='+j+'%');
setTimeout(function(){ $('b'+i).style.width = j+'%';},200);
}
}
This little bit of code is supposed to first make elements t0 visible, and then set the width of another element b0 in 1% steps with a time interval of 200ms, and then continue with t1,b1, t2,b2 etc etc.
But there's no 200ms delay, the whole code executes immediately.
--- EDIT ---
I have not explained very well, here's what I want to do:
1. show element Ax
2. increase element Bx width by 1% every 200ms until 50%
3. wait until element Bx reaches 50% before continuing
4. increment x
5. goto 1
Two problems:
The timeout functions see the wrong i and j values
They all run at the same time (200ms later)
The timeout functions see the wrong i and j values
The main problem is that the function you're passing into setTimeout has an enduring reference to the i and j variables, not copies of them as of when the function was created. That means all of the functions will see the value of i and j as of when they run, which will be 6 and 50 respectively. This is called "closing over" those variables (and the function is called a "closure").
The usual way to fix this is to create the functions in a way that they close over something that doesn't change. There are a couple of ways to do that; my favorite is to use a factory function:
function $(s) { return document.getElementById(s); }
var i = 0;
for (i=0; i<6; i++){
$('t'+i).className = 'show';
cl('t'+i);
for (var j=0; j<50; j++){
cl('b'+i+'='+j+'%');
setTimeout(makeHandler(i, j), 200);
}
}
function makeHandler(ivalue, jvalue) {
return function(){ $('b'+ivalue).style.width = jvalue+'%';};
}
Now we call makeHandler, and it returnns us a function that closes over ivalue and jvalue, which won't change. Or a refinement of the above which lets us dispose of the maker function when we're done with it:
function $(s) { return document.getElementById(s); }
var i = 0;
var makeHandler = function(ivalue, jvalue) {
return function(){ $('b'+ivalue).style.width = jvalue+'%';};
};
for (i=0; i<6; i++){
$('t'+i).className = 'show';
cl('t'+i);
for (var j=0; j<50; j++){
cl('b'+i+'='+j+'%');
setTimeout(makeHandler(i, j), 200);
}
}
makeHandler = undefined;
If you can rely on ES5 features (because of the environment you're targeting, or because you've included an ES5 shim), you can get much the same effect using the new Function#bind. Function#bind creates a new function just like makeHandler does, but there's always the possibility the engine can optimize a bit:
function $(s) { return document.getElementById(s); }
var i = 0;
for (i=0; i<6; i++){
$('t'+i).className = 'show';
cl('t'+i);
for (var j=0; j<50; j++){
cl('b'+i+'='+j+'%');
setTimeout(handler.bind(undefined, i, j), 200);
}
}
function handler(){
$('b'+ivalue).style.width = jvalue+'%';
}
The first argument to bind is what this should be in the function; in our case we don't care, so I've specified undefined (which means the function will get this referencing the global object — window, on browsers — unless this is strict mode code, in which case this will actually be undefined).
They all run at the same time (200ms later)
All of your functions are being scheduled to run 200ms after the above code. And so they will. :-) If you want to space them out, you'll want to increase that 200ms for each call to setTimeout. We can just multiply by i and j:
setTimeout(makeHandler(i, j), (200 * i * j) + 200);
Now the first one will run after 200ms, the second one 200ms later, etc. The whole thing will take about a minute to complete. This assumes you want the first element to grow, then the next, then the next, as opposed to all six of them growing in parallel with each other.
Alternately, you might want to have each function call its successor. That's probably what I'd do. So rather than scheduling 300 function calls, just schedule one and when it happens, schedule the next:
function $(s) { return document.getElementById(s); }
// Our counters are here
var i = 0, j = 0;
// This handles the outer portion of the loop (mostly)
function outer() {
$('t'+i).className = 'show';
cl('t'+i);
j = 0;
// Schedule the first inner portion 200ms from now
setTimeout(inner, 200);
}
// This handles the inner portion of the loop (mostly)
function inner() {
// Do this bit
$('b'+i).style.width = j+'%';
++j;
if (j < 50) {
// Continue the inner loop in 200ms
setTimeout(inner, 200);
}
else {
// Set up next outer loop
j = 0;
++i;
if (i < 6) {
// Not done yet, keep going
setTimeout(outer, 200);
}
}
}
// Kick it off
setTimeout(outer, 200);
In the above, I've also moved these lines:
$('t'+i).className = 'show';
cl('t'+i);
...into the delayed code, which I suspected was appropriate.
More to explore:
Closures are not complicated
It's because you're not closing over the i and j:
setTimeout(function(i, j) {
return function() {
$('b'+i).style.width = j+'%';
}
}(i, j), 200);
setTimeout(function () { alert('Refreshing…'); }, 2000);
It means after two seconds (2000 ms), show alert
I want to pass variable setTimeoutfunction and do something with that. When I alert value of i it shows me numbers that i did not expected. What i m doing wrong? I want log values from 1 till 8.
var end=8;
for (var i = 1; i < end; i ++) {
setTimeout(function (i) {
console.log(i);
}, 800);
}
The standard way to solve this is to use a factory function:
var end=8;
for (var i = 1; i < end; i ++) {
setTimeout(makeResponder(i), 800);
}
function makeResponder(index) {
return function () {
console.log(index);
};
}
Live example | source
There, we call makeResponder in the loop, and it returns a function that closes over the argument passed into it (index) rather than the i variable. (Which is important. If you just removed the i argument from your anonymous function, your code would partially work, but all of the functions would see the value of i as of when they ran, not when they were initially scheduled; in your example, they'd all see 8.)
Update From your comments below:
...will it be correct if i call it in that way setTimeout(makeResponder(i),i*800);?
Yes, if your goal is to have each call occur roughly 800ms later than the last one, that will work:
Live example | source
I tried setTimeout(makeResponder(i),setInterval(i));function setInterval(index) { console.log(index*800); return index*800; } but it's not work properly
You don't use setInterval that way, and probably don't want to use it for this at all.
Further update: You've said below:
I need first iteration print 8 delay 8 sec, second iteration print 7 delay 7 sec ........print 2 delay 2 sec ...print 0 delay 0 sec.
You just apply the principles above again, using a second timeout:
var end=8;
for (var i = 1; i < end; i ++) {
setTimeout(makeResponder(i), i * 800);
}
function makeResponder(index) {
return function () {
var thisStart = new Date();
console.log("index = " + index + ", first function triggered");
setTimeout(function() {
console.log("index = " +
index +
", second function triggered after a further " +
(new Date() - thisStart) +
"ms delay");
}, index * 1000);
};
}
Live example | source
I think you now have all the tools you need to take this forward.
Your problem is that you are referring to the variable i some time later when your setTimeout() function fires and by then, the value of i has changed (it's gone to the end of the for loop. To keep each setTimeout with it's appropriate value of i, you have to capture that value i separately for each setTimeout() callback.
The previous answer using a factory function does that just fine, but I find self executing functions a little easier than factory functions to type and follow, but both can work because both capture the variables you want in a closure so you can reference their static value in the setTimeout callback.
Here's how a self executing function would work to solve this problem:
var end=8;
for (var i = 1; i < end; i ++) {
(function (index) {
setTimeout(function() {
console.log(index);
}, 800);
})(i);
}
To set the timeout delay in proportion to the value of i, you would do this:
var end=8;
for (var i = 1; i < end; i ++) {
(function (index) {
setTimeout(function() {
console.log(index);
}, index * 800);
})(i);
}
The self executing function is passed the value of i and the argument inside that function that contains that value is named index so you can refer to index to use the appropriate value.
Using let in ES6
With the ES6 of Javascript (released in 2015), you can use let in your for loop and it will create a new, separate variable for each iteration of the for loop. This is a more "modern" way to solve a problem like this:
const end = 8;
for (let i = 1; i < end; i++) { // use "let" in this line
setTimeout(function() {
console.log(i);
}, 800);
}
The main reason for this to not to work, is because, of the setTimeout which is set to run after 800 and the scope of i.
By the time it executes which the value of i will already have changed. Thus no definitive result could be received. Just like TJ said, the way to work this around is through a handler function.
function handler( var1) {
return function() {
console.log(var1);
}
}
var end = 8;
for (var i = 1; i < end; i++) {
setTimeout(handler(i), 800);
}
Demo
setTimeout accepts variables as additional arguments:
setTimeout(function(a, b, c) {
console.log(a, b, c);
}, 1000, 'a', 'b', 'c');
Source.
EDIT: In your example, the effective value of i will likely be 8, since the function is merely to be called after the loop has finished. You need to pass the current value of i for each call:
var end=8;
for (var i = 1; i < end; i ++) {
setTimeout(function (i) {
console.log(i);
}, 800, i);
}