I would like to name many setTimeouts differently, so that I can clear each one individually. I tried the following approach:
for(i=0; i<5; i++){
var wordTimeout = i + "_T_i_m_e_o_u_t";
clearTimeout( wordTimeout );
var wordTimeout = setTimeout( function() { console.log( i + ' timeout called' );}, 2000);
console.log(i);
}
http://jsfiddle.net/NU4E8/2/
But it looks like I renamed wordTimeout, and then the variable didn't matter. Ideally, in this example all of the setTimeouts would have been individually cleared after being called. So no timeouts would show up in the log. Thanks.
You can't name your own timeouts. setTimeout and setInterval return a numeric ID, which is what must be passed into clearTimetout. You can, however, use an array or an object to store your timeouts:
var timeoutIds = [];
for(var i=0; i<5; i++){
timeoutIds.push(setTimeout(function(){ /* ... */ }));
}
Or, if you need to name them, you can do it like this:
var timeoutIds = {};
for(var i=0; i<5; i++){
timeoutIds['timeout_' + i] = setTimeout(function(){ /* ... */ });
}
You should also be aware that in your loop, as you've written it, all the timeouts will print '4 timeout called'; that's because the functions are declared in a closure that contains i, and by the time they get around to executing, the loop will have finished, and have the final value 4 (unless it's re-used by other code in the same scope, of course). What you probably want to do is this:
var timeoutIds = [];
for(var i=0; i<5; i++){
timeoutIds.push(
(function(i){
// i now captured as a function-scope variable
return setTimeout(function(){ console.log(i + ' timeout called'); });
})(i)
);
}
This creates an immediately-invoked function expression (IIFE), which "captures" the value of i as a function-scope variable.
You can create an array and store the timer references there using the index as the key like
var timers = [];
for (i = 0; i < 5; i++) {
timers.push(setTimeout(function () {
console.log(i + ' timeout called');
}, 2000));
console.log(i);
}
then access the timer reference for i=0 using timers[0]
You have another common problem known as usage of a closure variable in a loop
Javascript closure inside loops - simple practical example
Creating closures in loops: A common mistake
Create an array where you will store the numbers returned by each of the setTimeout calls.
var timeouts=[];
for(i=0; i<5; i++){
clearTimeout(timeouts[i]);
timeouts[i]=setTimeout((function(){
console.log(this.i+' timeout called');
}).bind({i:i}),2000);
console.log(i);
}
Then, when you need to do the clearing, you will have the timeout numbers stored by i.
The .bind trick is because of i's scope.
Related
var funcs=[];
for(var i=0;i<3;i++){
funcs[i]=function(){
console.log(i);
};
}
for(var j=0;j<3;j++){
funcs[j]();
}
In this way ,I know it will alert 3 all.Because the functions were called after i was assigned to 3.
But in the below code, I can't understand why this happens.
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 2000);
}
In my oppinion, when i was assigned to 0, the first function setTimeout would be executed before i was assigned to 1.
Am I wrong in the order of this loop?
console.log(i) will be called after the for loop finished, the for loop which because of the way var is function scoped. Will be 10 by the time (2000ms) it reaches the console.log statement. One way to give the expected result is using let instead of var. However you are removing some browser support with using let.
Good reading on this topic would be getify's "You Don't Know Js" book.
for(let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 2000);
}
You need to put the i variable into a closure, otherwise, it will be rewritten in each loop iteration. The same occurs for the setTimeout function, you should put the i variable into a closure as well.
var funcs=[];
for(var i=0;i<3;i++){
(function(i){
funcs[i]=function(){
console.log(i);
};
})(i);
}
for(var j=0;j<3;j++){
funcs[j]();
}
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
Can anyone explain why document.write part always outputs 10?
function creatFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
}
}
return result;
}
var funcs = creatFunctions();
for (var i = 0; i < funcs.length; i++) {
document.write(funcs[i]() + "<br />");
}
I think the answer to this question is more thorough than the duplicate question, therefore worth keeping this post.
Sure.
In your for loop, you reference i. What you expect to be happening is that each closure gets a snapshot of i at the time the function is created, therefore in the first function it would return 0, then 1, etc.
What's really happening is that each closure is getting a reference to the external variable i, which keeps updating as you update i in the for loop.
So, the first time through the loop, you get a function that returns i, which at this point is 0. The next time you get two functions which return i, which is now 1, etc.
At the end of the loop, i==10, and each function returns i, so they all return 10.
UPDATE TO ADDRESS QUESTION IN COMMENT:
It's a little confusing since you use i in two different contexts. I'll make a very slight change to your code to help illustrate what's going on:
function creatFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
}
}
return result;
}
var funcs = creatFunctions();
// NOTE: I changed this `i` to `unrelated_variable`
for (var unrelated_variable = 0; unrelated_variable < funcs.length; unrelated_variable++) {
document.write(funcs[unrelated_variable]() + "<br />");
}
... the functions that you create in your creatFunctions() function all return i. Specifically, they return the i that you create in the for loop.
The other variable, which I've renamed to unrelated_variable, has no impact on the value returned from your closure.
result[i] = function () {
return i;
}
... is not the same thing as result[2] = 2. It means result[2] = the_current_value_of_i
Because you reference i as a variable, which, when the function executes, has the value 10. Instead, you could create a copy of i by wrapping in in a function:
(function (i) {
result[i] = function () {
return i;
}
}(i));
The i that you are returning inside the inner function - is the i that was declared inside the for(var i=0...) therefor - it is the same i for all of the functions in result
and by the time you are calling the functions its value is 10 (after the loop ends)
to accomplish what you wanted you should declare another variable inside the scope of the anonymous function
Because reference to "i" is boxed (enclosed) in your closure that you defined.
result[i] = function () {
return i;
}
And it just keeps reference to the "i" var which keeps changing with each iteration.
UPDATE
This is related to the this keyword function scope which defines variable context.
Hence, in JavaScript, there's no such thing as block scope (for, if else, while, ...).
With that in mind, if you assign var funcs to a function definitioncall, the function body (this) will be bound to the global scope var i inside that function will reach the end of the for loop and stays there in memory. In this case i is 10.
Unfortunately for you, since function scope puts every variable to the top, var i will be global as well.
Although I was completely wrong at first, I stand corrected and took the liberty to create the output of your script in this demo.
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);
}