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
I need some explanation to understand clearly what is happening here ;
We have these two code samples , the first one logs to the console -1 five times ,and that is because the for loop executes completely leaving i with the value –1, and only then do the callbacks start executing. The problem
is, when they execute, i already has the value –1.
The second sample logs the expected result which is a 5 to 1 countdown.The only difference between the two samples is that i is no longer declared in the scope of the countdown() function , but why does that change the execution and how is the value of i being handled in the second sample?
Code Sample #1
function countdown() {
let i; // note we declare let outside of the for loop
console.log("Countdown:");
for(i=5; i>=0; i--) {
setTimeout(function() {
console.log(i===0 ? "GO!" : i);
}, (5-i)*1000);
}
}
countdown();
Code Sample #2
function countdown() {
console.log("Countdown:");
for(let i=5; i>=0; i--) { // i is now block-scoped
setTimeout(function() {
console.log(i===0 ? "GO!" : i);
}, (5-i)*1000);
}
}
countdown();
In sample 1, the variable is declared just inside the function. You get a new i every time countdown() is called. Within countdown() the variable changes. By the time the timeout runs, i will be on its lowest value.
In sample 2, the variable is declared just inside the loop. You get a new i every time the loop goes around. This means you get a new i for every function you pass to setTimeout. The different timeouts are no longer sharing the same variable.
The key difference is the use of the let keyword within the for loop.
In code sample #1, i is declared outside of the for loop. That means each access to i in each closure causes the same i to be dereferenced, because i is within the identical block scope of each closure, which is why we get -1 each time.
In code sample #2, i is declared within the for loop, which means a new i is created for each execution. Each closure refers its own, exclusive block in the for loop which has a unique i. Thus, we get the expected 5 to 1 countdown.
For use of ES6 features like let, it can be handy to use the Babel REPL to determine how scoping rules are applied. The following simplified versions of code blocks #1 and #2:
for (var j = 0; j < 5; ++j) {
setTimeout(function() { console.log(j); }, 1);
}
for (let i = 0; i < 5; ++i) {
setTimeout(function() { console.log(i); }, 1);
}
...are compiled into:
"use strict";
for (var j = 0; j < 5; ++j) {
setTimeout(function () {
console.log(j);
}, 1);
}
var _loop = function _loop(i) {
setTimeout(function () {
console.log(i);
}, 1);
};
for (var i = 0; i < 5; ++i) {
_loop(i);
}
Notice how, for code block #2, i gets copied into a distinct function scope using the _loop function where each closure refers to its own i.
Check it out here.
I did some research about loops and setTimeout function, but still its not working as I wish...
It opens all the links in same time without delay of 5 seconds per link that opened.
I would like it to open every link with 5 second delay after every each of them.
Code:
var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]')
for (var i = 1; i <= links.length; i++) {
(function(index) {
setTimeout(function() {
window.open(links[index].href,'_blank');
}, 5000);
})(i);
}
Using a Promise chain and Array#reduce(), you can do this:
var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]');
Array.from(links).reduce((chain, { href }) => {
return chain.then(() => new Promise(resolve => {
window.open(href, '_blank');
setTimeout(resolve, 5000);
}));
}, Promise.resolve())
If you don't want to do something quite this fancy and you're fine setting all your timeouts at once, you can simplify your for loop using let instead of var and an IIFE:
var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]');
for (let i = 0; i < links.length; i++) {
setTimeout(function() {
window.open(links[i].href, '_blank');
}, 5000 * i);
}
Or even simpler, using for...of and object destructuring:
var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]');
var i = 0;
for (const { href } of links) {
setTimeout(function() {
window.open(href, '_blank');
}, 5000 * i++);
}
That's because all your timeouts are set immediately, almost at one time. So the ends of timeouts are take place almost on the same time. Try this:
var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]')
for (var i = 1; i <= links.length; i++) {
(function(index) {
setTimeout(function() {
window .open(links[index].href,'_blank');
}, 5000 * i);
})(i);
}
setTimeout is asyncronous. I would solve this specified problem with:
Array.from(links).reduce((a,e)=>{
setTimeout(function() { window .open(e.href,'_blank');}, a);
return a + 5000;
} ,5000);
From the code above i assume that you wish to open the links once every 5 seconds so here are the improvements and suggestion made upon your code
// use JS hoisting for variable declarations
var
// Timer to store the next timer function reference
timer,
// The delay between each function call
delay = 5000,
// Set the desired selector
selectors = 'a[class="mn-person-info__link ember-view"][id^="ember"]',
// Get the list of the required selectors.
links = document.querySelectorAll(selectors);
// Create a function which will be called every X seconds
function openLink( index ){
// validate that the index is not out of bound
if (index === links.length){
return;
}
// get the current link and open new window with the link url
window.open(links[index].href,'_blank');
// Set the next timer to open the next window
timer = setTimeout( openLink, delay, ++index);
}
// Call the function for the first time with index = 0
openLink( 0 );
What does this code do?
The first section is declaration of the variables which will be used in this script. The preferred way to declare variables id to to use hoisting
Hoisting
Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution.
Timers
If you wish to open the links in a sequence you should put them inside a function which will call them one after the other instead of using the for loop. The for loop will place all of them in the call stack/event loop and all of them will be executed after 5000 milliseconds since this is what set time out will so, it will schedule the execution of the code to 5000 milliseconds from now for all of them.
Im recommending you to watch the amazing lecture by Philip Roberts
Philip Roberts: What the heck is the event loop anyway
Saving the return value from the setTimeout will allow you to later on cancel the timer if you wish using the clearTimeout( timer )
Summary
Since you had a for loop it will simply loop over all your links. The setTimeout will set the scheduled execution time 5 seconds from now for all the links.
The trick here is to set the next timer after the current one is opened. This is why the sertTimeout is defined within the function itself.
The setTimeout gets a third param which is the parameter passed to the function when its being called.
Easy way:
links.forEach(function(i, link) {
setTimeout(function() {
window.open(link.href,'_blank');
}, 5000 * i);
});
Just wait i * 5 seconds, where i is the index of the link
Learning about callbacks and the solution to this problem eludes me.
It should print a number every second counting down until zero. Currently, it logs the numbers from 10 - 0 but all at once and continues in an infinite loop.
Please help me to gain a better understanding of this situation. I have read up on callbacks and have a conceptual understanding but execution is still a bit tricky.
var seconds = 0;
var countDown = function(){
for(var cnt = 10; cnt > 0; cnt--){
setTimeout(function(x){
return function(){
seconds++
console.log(x);
};
}(cnt), seconds * 1000);
}
}
countDown()
The way your code is working now, it executes a for loop with cnt going from 10 to 1. This works. On each iteration, it schedules a new function to be run in seconds * 1000 milliseconds, carefully and properly isolating the value of cnt in x each time. The problem is that seconds is 0, and it will only be changed once a callback executes; but by the time a callback executes, all of them will already have been scheduled for execution. If you need seconds * 1000 to vary while you’re still scheduling them all (while the for loop is still running), you need to change seconds in the loop, rather than inside one of the callbacks.
Read up on IIFEs to see how they work. In this situation, you're creating a closure of the value you want to print. You had the right idea, but your syntax was off.
var seconds = 0;
var countDown = function () {
var cnt = 10;
// simplify your loop
while (cnt--) {
// setTimeout expects a function
// use an IIFE to capture the current value to log
setTimeout((function (x) {
// return the function that setTimeout will execute
return function (){
console.log(x + 1);
};
}(cnt)), (++seconds) * 1000);
}
};
countDown();
Why console.log(1) gets executed here forever:
var interval = setInterval(function() {
if (true) {
clearInterval(interval);
console.log(1);
}
}, 100);
It depends on the scope within which you're executing this code.
If interval is unique within its scope — be it global or function scope — then this will work as expected.
If, however, you execute this code within a loop (for example), then you are overwriting interval with some new interval on each iteration, breaking your clearInterval call for all but the very last setInterval call:
for (var i = 0; i < 3; i++) {
var interval = setInterval(function() {
if (true) {
clearInterval(interval);
console.log(1);
}
}, 100);
}
// ^ will give you one single console log entry,
// and two more console log entries per second forever
It's seems that your variable interval is used somewhere again. If I run code you provided it works as expected. I guess user Lightness has given a great explaination of this, also he provided piece of code where "closure problem" is obvious (which caused you problem too). I just want to add extra information. If you want your code inside of loop + setInteval works aparat you can do the following:
for (var i = 0; i < 3; i++) {
var o = {
i: i,
interval: null,
timer: function() {
if (true) {
clearInterval(this.interval);
console.log(this.i);
}
}
};
o.interval = setInterval(o.timer.bind(o), 1000);
}
DEMO
I hope it will be useful for someone.
Suppose you have 3 arrays you want to loop over, with lengths x, y, and z, and for each loop, you want to update a progress bar. For example:
function run() {
x = 100;
y = 100;
z = 10;
count = 0;
for (i=0; i<x; i++) {
//some code
for (j=0; j<y; j++) {
// some code
for (k=0; k<z; k++) {
//some code
$("#progressbar").reportprogress(100*++count/(x*y*z));
}
}
}
}
However, in this example, the progress bar doesn't update until the function completes. Therefore, I believe I need to use setTimeout to make the progress bar update while the function runs, although I'm not sure how to do that when you have nested for loops.
Do I need to break each loop up into its own function, or can I leave them as nested for loops?
I created a jsfiddle page in case you'd like to run the current function: http://jsfiddle.net/jrenfree/6V4Xp/
Thanks!
TL;DR: Use CPS: http://jsfiddle.net/christophercurrie/DHqeR/
The problem with the code in the accepted answer (as of Jun 26 '12) is that it creates a queue of timeout events that don't fire until the triple loop has already exited. You're not actually seeing the progress bar update in real-time, but seeing a late report of what the values of the variables were at the time they were captured in the inner closure.
I'd expect that your 'recursive' solution looks a bit like using continuation-passing style to ensure that your loop doesn't continue until after you've yielded control via setTimeout. You might not know you were using CPS, but if you're using setTimeout to implement a loop, you're probably pretty close to it.
I've spelled out this approach for future reference, because it's useful to know, and the resulting demo performs better than the ones presented. With triple nested loops it looks a bit convoluted, so it may be overkill for your use case, but can be useful in other applications.
(function($){
function run() {
var x = 100,
y = 100,
z = 10,
count = 0;
/*
This helper function implements a for loop using CPS. 'c' is
the continuation that the loop runs after completion. Each
'body' function must take a continuation parameter that it
runs after doing its work; failure to run the continuation
will prevent the loop from completing.
*/
function foreach(init, max, body, c) {
doLoop(init);
function doLoop(i) {
if (i < max) {
body(function(){doLoop(i+1);});
}
else {
c();
}
}
}
/*
Note that each loop body has is own continuation parameter (named 'cx',
'cy', and 'cz', for clarity). Each loop passes the continuation of the
outer loop as the termination continuation for the inner loop.
*/
foreach(0, x, function(cx) {
foreach(0, y, function(cy) {
foreach(0, z, function(cz) {
count += 1;
$('#progressbar').reportprogress((100*(count))/(x*y*z));
if (count * 100 % (x*y*z) === 0) {
/*
This is where the magic happens. It yields
control to the javascript event loop, which calls
the "next step of the foreach" continuation after
allowing UI updates. This is only done every 100
iterations because setTimeout can actually take a lot
longer than the specified 1 ms. Tune the iterations
for your specific use case.
*/
setTimeout(cz, 1);
} else {
cz();
}
}, cy);
}, cx);
}, function () {});
}
$('#start').click(run);
})(jQuery);
You can see on jsFiddle that this version updates quite smoothly.
If you want to use setTimeout you could capture the x, y, z and count variables into a closure:
function run() {
var x = 100,
y = 100,
z = 10,
count = 0;
for (var i=0; i<x; i++) {
for (var j=0; j<y; j++) {
for (var k=0; k<z; k++) {
(function(x, y, z, count) {
window.setTimeout(function() {
$('#progressbar').reportprogress((100*count)/(x*y*z));
}, 100);
})(x, y, z, ++count);
}
}
}
}
Live demo.
Probably a jquery function in reportprogress plugin uses a setTimeout. For example if you use setTimeout and make it run after 0 milliseconds it doesn't mean that this will be run immediately. The script will be executed when no other javascript is executed.
Here you can see that i try to log count when its equal to 0. If i do it in setTimeout callback function then that is executed after all cycles and you will get 100000 no 0. This explains why progress-bar shows only 100%. js Fiddle link to this script
function run() {
x = 100;
y = 100;
z = 10;
count = 0;
for (i=0; i<x; i++) {
//some code
for (j=0; j<y; j++) {
// some code
for (k=0; k<z; k++) {
//some code
if(count===0) {
console.log('log emidiatelly ' + count);
setTimeout(function(){
console.log('log delayed ' + count);
},0);
}
count++;
}
}
}
}
console.log('started');
run();
console.log('finished');
wrapping everything after for(i) in setTimeout callback function made the progress-bar work. js Fiddle link
Edit:
Just checked that style setting code for item is actually executed all the time. I think that it might be a browser priority to execute javascript first and then display CSS changes.
I wrote a another example where i replaced first for loop with a setInterval function. It's a bit wrong to use it like this but maybe you can solve this with this hack.
var i=0;
var interval_i = setInterval(function (){
for (j=0; j<y; j++) {
for (k=0; k<z; k++) {
$("#progressbar").reportprogress(100*++count/(x*y*z));
}
}
i++;
if((i<x)===false) {
clearInterval(interval_i);
}
},0);
JS Fiddle
I've found a solution based on the last reply but changing the interval time to one. This solution show a loader while the main thread is doing an intensive task.
Define this function:
loading = function( runme ) {
$('div.loader').show();
var interval = window.setInterval( function() {
runme.call();
$('div.loader').hide();
window.clearInterval(interval);
}, 1 );
};
And call it like this:
loading( function() {
// This take long time...
data.sortColsByLabel(!data.cols.sort.asc);
data.paint(obj);
});