Why does variable in setTimeout callback not have expected value? - javascript

<div id="image_cont">
<img src="images/pic1.jpg" alt="pic1" />
<img src="images/pic2.jpg" alt="pic2" />
<img src="images/pic3.jpg" alt="pic3" />
</div>
$(document).ready(function() {
slide(3, "image_cont", 5000, 600);
});
function slide(numberOfImages, containerId, timeDelay, pixels) {
//start on first image
var i = 0;
var style = document.getElementById(containerId).style;
window.setInterval(function() {
if (i >= numberOfImages){
i = 0;
}
var marginLeft = (-600 * i);
var pixelMovement = pixels/15;
////////////////////////////////////////LOOK HERE//////////////////////////////
for (var j = 0; j * pixelMovement < 600; j++){
window.setTimeout(function(){
//alert('marginLeft: ' + marginLeft + ' j: ' + j + ' pixelMovement: ' + pixelMovement);
//this alert shows j is 15 when it should be 0, what's going on?
/////////////////////////////////////////END//////////////////////////////////
style.marginLeft = (marginLeft - j * pixelMovement) + "px";
}, 150);
}
i++;
}, timeDelay);
}

You can't use the variable j directly in the setTimeout function because that function runs some time later after your for loop has completed and thus j has the terminating value, not the value when you called setTimeout.
You can capture the current value of j in a function closure that would be available in the setTimeout function like this:
for (var j = 0; j * pixelMovement < 600; j++){
window.setTimeout(function(cntr) {
return function() {
style.marginLeft = (marginLeft - cntr * pixelMovement) + "px";
};
} (j), 150);
}
I find this type of closure kind of confusing. I'll try to explain what's happening. We pass to the setTImeout() function the result of executing an anonymous function that takes one parameter (which I named cntr here) and we pass the value of j as the value of that parameter. When that function executes (which now has the value of j available inside it), that function returns another anonymous function. This other anonymous function is what setTimeout will actually call when it fires. But, this second anonymous function is inside a function closure from the first function that has the captured value of j in it (as a variable that I renamed cntr while inside the function closure to avoid confusion in explaining it). It's the anonymous functions that make it so confusing, but it works.

It should be anything, because window.setTimeout is an "async" function so it returns immediately after executing.
So in your code the for loop keeps looping and after a while (150ms) your function is being executed and the j variable's actual value is printed out.

The functions you create and pass to setTimeout are all referencing the same loop counter variable j - after the loop has run its course, j will be set at the value which caused the loop to terminate. You need to use a closure to ensure the functions you're creating in the inner loop have access to the value j had at the time the function was defined.
See this answer for more detail on the problem and the solution:
Async calls with async response in NodeJS
(Silly variable name to make it clear that it's distinct from j):
window.setTimeout((function(jWhenFunctionWasDefined) {
return function() {
style.marginLeft = (marginLeft - jWhenFunctionWasDefined * pixelMovement) + "px";
}
})(j), 150)

Related

Why would setTimeout inside a for loop not respect the time I have set for the function to execute

function generalFunctionThatHappensWhenYouClickAButton(){
for (let i = 0; i < 1000; i++){
setTimeout(animateSquares(), 10000);
}
}
function animateSquares(){
topPositionThatAdds++;
console.log(topPositionThatAdds);
}
The function animateSquares() will execute 1000 times as planned, but all at the same time and not waiting 10,000 ms for each execution, as I planned to.
Does anyone know why this is happening?
Thank you for your time.
Well, that is because inside your setTimeout function, you're not just specifying your handler/callback function, instead you are calling it. In case of any callback functions, if you call them using ( ), they will always be executed immediately.
Therefore, your code shouldn't be this:
setTimeout(animateSquares(), 10000);
Instead, it should be this:
setTimeout(animateSquares, 10000); //specify the callback, don't call it using ( )
As you want there to be a 10ms delay between each callback execution, you can go about that in the following way:
let topPositionThatAdds = 0;
function generalFunctionThatHappensWhenYouClickAButton() {
for (let i = 0; i < 1000; i++) {
setTimeout(animateSquares, 10000 * (i + 1));
}
}
function animateSquares() {
topPositionThatAdds++;
console.log(
topPositionThatAdds
);
}
generalFunctionThatHappensWhenYouClickAButton();
Now if you wanted to use a parameter in your callback (topPositionThatAdds), then you can do something like the following.
function topPositionThatAdds(i){
console.log('square ', i);
};
function generalFunctionThatHappensWhenYouClickAButton() {
for(var i = 0;i < 1000; i++){
let j = i;
setTimeout(
function() {topPositionThatAdds(j)}
, 10000 * (j + 1));
}
}
generalFunctionThatHappensWhenYouClickAButton();

Increment loop but it increments at the end

I’m trying to use an increment loop but I want it to increment at the end of the loop. Sadly, whenever I simply put the i++ at the end of the loop it doesn’t behave like I’d expect or want it to. Anyone mind showing me the proper way of doing it?
The referred increment loop:
for (i = 1; i < 15; i++) {
// do somthing here
}
Here is the loop I’m working with:
for (i = 1; i < 15; i++) {
for (x = 1; x < 15; x++) {
var take = document.getElementById("row" + i + "sm" + x);
Tesseract.recognize(take)
.then(function(result) {
console.log(result.text);
// rows[i][x] = result.text;
})
}
}
What I’d like it to do:
for (i = 1; i < 15) {
for (x = 1; x < 15) {
var take = document.getElementById("row" + i + "sm" + x);
Tesseract.recognize(take)
.then(function(result) {
console.log(result.text);
//rows[i][x] = result.text;
x += 1;
})
i += 1;
}
}
I am using the for loop because I need to iterate over something one by one. How do I properly increment i at the end of the loop?
Here is a video explaining my problem with context and explanation why it is not an ASYNC problem. Sorry if it is hard to follow, ill update it with audio soon so I can explain it propperly.
https://drive.google.com/file/d/1n1ZwNJif5Lb5zfLb2GPpBemObwpOqNf7/view
The problem is that the second one doesnt wait until first one is complete.
You can try with recursion inside then. There maybe some mistake with i,x but you get the point.
You execute first with i=1 and x=1, after the operation is done (then) you call the next until all elements are executed.
function execItem(i, x) {
var take = document.getElementById("row" + i + "sm" + x);
Tesseract.recognize(take)
.then(function(result){
rows[i][x] = result.text;
if (i < 15 && x < 15) {
if (i > 15) {
x += 1
i = 1
} else {
i += 1
}
execItem(i, x)
}
})
}
execItem(1, 1)
As a comment suggests this actually seems likely to be a problem with an asynchronous call (Tesseract...then) inside a loop. By the time the function inside then is called, your values of x and i have already moved on, so you don't get the result you expect.
One way around this would be to use a 'closure' - making a function that creates another function based on the value of i and x.
function getDisplayFunc(row, col) {
function displayRecognisedText(result) {
console.log(row, col, result.text);
//rows[row][col] = result.text;
}
return displayRecognisedText;
}
for (i = 1; i < 15; i++) {
for (x = 1; x < 15; x++) {
var take = document.getElementById("row" + i + "sm" + x);
Tesseract.recognize(take).then(getDisplayFunc(i, x));
}
}
I guess #Mike spot the error on: your code is asynchronous. What does it mean?
So, let's suppose you have this loop:
for (i = 0; i < 10; i++) {
console.log(i);
}
It will print this, right?
0
1
2
3
4
5
6
7
8
9
However, you do not print your value inside the loop directly, but as a follow-up operation to a promise. This makes this code asynchronous. It means that it does not have to execute at the exact moment you call it. I do not have Tesseract here so I will make my loop asynchronous using another very old trick, setTimeout():
for (i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 0);
}
If I run it, I get this:
10
10
10
10
10
10
10
10
10
10
What happens is, when I pass the operation we want to do (in this case, printing the i value) to an asynchronous function (recognize().then() in your case, setTimeout() in my case) through a callback (function() {console.log(i);} in my example) the asynchronous function "schedules" the operation to execute as soon as possible, but this "soon" is not faster than the loop. So, the loop finishes executing but our callback is not called, not even once! Since you are not declaring i with let, it is a global variable, so there exists only one i. And since the loop finished, the value of the i variable is 10 already.
It used to be a hard thing to solve, but with ES6 it is quite straightforward: declare i with let!
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 0);
}
The let-ed variable has a new binding at each iteration of the loop, so in practice you have 10 variables called i. The closure of your function will have access only to the one with the right value!
Maybe you should try to use while loop.
Like this:
while i < 15:
//do something
i += 1
For two variables: x, i with embeding:
while x < 15:
//do something
while i < 15:
//do something2
i += 1
x += 1
Hope I understand the problem correctly.

Using setTimeout and an integer in a for loop

I have a button with id play.
I want a countdown on that button with this code. But for some reason I can't get this to work.
var timeoutTime = 500, seconds = 5;
var countdown = $("#play h4");
for(var i = seconds; i>0; i--)
{
setTimeout(function() {
countdown.text("" + i); },timeoutTime);
timeoutTime += 1000;
}
I tried a lot of things, the best I could get was just a 1 instead of 5 4 3 2 1. With this code I get a 0 on the button.
What's the problem ?
Use this :
for(var i = seconds; i>0; i--) {
(function(i){
setTimeout(function() {
countdown.text("" + i); }, timeoutTime);
})(i);
timeoutTime += 1000;
}
Your problem was that i changes and you were always calling with the last value of i, because the callback you pass to setTimeout is called after the loop finishes.
The classic solution is to use a closure to keep another variable (here with the same name i) having the desired value. It works because this is a different function for each iteration (the scope of a variable is either the global scope or the function where it is declared).

Can't manage to sleep inside a loop

I want to pause 1 second for every time it loops, it is usually easy to do similar pauses on other cases, but when working with loops, it seems it get harder:
for (var i=0 ; i < 10 ; i++) {
document.write (i + "<br>");
// I want to wait 1 second here
}
This is one example of my thousands failed attempts:
function writeMsg (index) {
document.write (index + "<br>");
}
for (var i=0 ; i < 10 ; i++) {
setTimeout (writeMsg(i), 1000);
}
Any ideas of how to get this to work?
This function works more like a normal for loop while it isn't
You need to take into account that a for gets 3 arguments inbetween semicolons.
Before starting (ie var i=0 you define a variable)
A condition before running the code again (ie i < 10 while i is under 10)
An action everytime it finishes the code again (i++ add one to i)
Code
(function() {
// Define a variable
var i = 0,
action = function() {
// Condition to run again
if (i < 10) {
document.write(i + "<br>");
// Add one to i
i++;
setTimeout(action, 1000);
}
};
setTimeout(action, 1000);
})();
Here is a jsfiddle for this code demonstrating its working:
http://jsfiddle.net/sg3s/n9BNQ/
You pass the return value of a function call to setTimeout instead of a function. Try the following code:
for (var i = 0; i < 10; i++) {
(function(i) {
setTimeout(function() {
writeMsg(i);
}, 1000*i);
})(i);
}
In case you wonder why the call is wrapped inside an anonymous function: Without that function each setTimeout callback would receive the same i so when the callbacks fire it would always be 10. The anonymous function creates a new i inside that is not connected to the loop variable.
Classic function-in-a-loop problem. One archetypal solution:
function createCallback(i) {
return function () {
writeMsg(i);
};
}
function writeMsg (index) {
document.write (index + "<br>");
}
for (var i=0 ; i < 10 ; i++) {
setTimeout (createCallback(i), 1000*i);
}
The 10 timeouts are all based on the time that setTimeout() is called. So, they are all triggered at the same time.
for (var i=0; i < 10; i++) {
(function(idx){
setTimeout(function(){
document.write(idx+"<br/>");
},1000*idx);
})(i);
};
try this it will definitely help who all are think how to make it work wait property inside For Loop...
try this code in this URL http://www.shopjustice.com/the-collections/C-10329.
var var2;
var tmp;
var evt;
var i=0;
var res = document.getElementsByClassName('mar-plp-filter-content nav nav--stacked')[0].children.length;
tmp = document.getElementsByClassName('mar-plp-filter-content nav nav--stacked')[0].children;
function myfunc()
{
if(i<res)
{
var2 = tmp[i].getElementsByTagName("span")[0].getElementsByClassName("inverted")[0];
// alert(var2.innerHTML);
var evObj = document.createEvent('MouseEvents');
evObj.initEvent( 'mouseover', true, false );
var2.dispatchEvent(evObj);
var2.style.backgroundColor="GREEN";
i++;
setTimeout(myfunc,3000);
}
};
setTimeout(myfunc,3000);

Problem with setTimeout()

This is my code. What I want it to do is write 0, wait one sec, write 1, wait one sec, write 2, wait one sec, etc. Instead it writes 5 5 5 5 5
for(i = 0; i < 5; i++) {
setTimeout("document.write(i + ' ')", 1000);
}
http://jsfiddle.net/Xb7Eb/
1) You set all the timeouts to last 1 second at the same time. The loop doesn't wait for the timeout to occur. So you have 5 timeouts that all execute at the same time.
2) When the timeouts execute, the loop is long since complete and i has become 5. So once they execute, they all print "5"
3) document.write() writes somthing onto the page, in the same place it executes. I.e. if you have <script>document.write("xyz")</script> in the middle of a piece of text, it'll write "xyz" in the middle of the text. The timeouts, however, are not necessarily anywhere on the page. They exist only in code.
Here's a solution that's as close to yours as possible: http://jsfiddle.net/rvbtU/1/
var container = document.getElementById("counter");
for(i = 0; i < 5; i++) {
setTimeout("container.innerHTML += '" + i + " ';", 1000 * i);
}
However, that solution uses setTimeout's ability to evaluate a string as javascript, which is never a good idea.
Here's a solution that uses an anymous function instead: http://jsfiddle.net/YbPVX/1/
var container = document.getElementById("counter");
var writer = function(number) {
return function() { container.innerHTML += String(number) + " "; };
}
for(i = 0; i < 5; i++) {
setTimeout(writer(i), 1000 * i);
}
Edit: Forgot to save the 2nd fiddle. Whoops. Fixed now.
Most of the answers available are giving bad advice.* Specifically, you shouldn't be passing a string to setTimeout anymore (it still works, but it's discouraged), it's no longer 2000, there are better ways to do this.
setTimeout takes a function as the first parameter, and that's what you should do, however there are some issues when calling setTimeout in a loop.
This looks like it should work:
var i;
for ( i = 0; i < 5; i++ )
{
setTimeout(function(){
document.write( i + ' ' );
}, 1000 * (i + 1) );
}
But it doesn't. The issue is that by the time setTimeout executes the function, the loop will have incremented i to 5, so you'll get the same value repeated.
There are a few fixes. If you're willing to risk a with statement, you could try the following:
var i;
for ( i = 0; i < 5; i++ )
{
with( { i:i } )
{
setTimeout(function(){
document.write( i + ' ' );
}, 1000 * (i+1) );
}
}
Note that with is typically discouraged just like passing string values to setTimeout, so I don't really suggest this method of doing things.
The better way is to use a closure:
var i;
for ( i = 0; i < 5; i++ )
{
(function(i){
setTimeout(function(){
document.write( i + ' ' );
}, 1000 * (i+1) );
})(i);
}
To explain what's going on, the anonymous function wrapper (function(i){...code...}) executes immediately because it's wrapped in parens and passed i as a value:
(function(i){...code...})(i);
This forces the i variable that document.write uses to be a different one than what's being used in the for loop. You could even change the parameter used in the anonymous function wrapper if the difference gets too confusing:
(function(a){document.write(a+' ')})(i);
* when I started writing this question there were a number of answers describing how to fix the string to work with setTimeout, although they would technically work, they didn't include why they would work (because 'document.write("' + i + ' ");' evaluates i at the time of calling due to string concatenation, versus evaluating i at runtime like the previous version did), and they most certainly didn't mention that it's the bad old way of calling setTimeout.
try
var i = 1;
function timeout(){
document.write(i + ' ');
i++;
if (i == 5) return;
setTimeout(timeout, 1000);
}
timeout();
http://jsfiddle.net/nnJcG/1/
You have a problem with clousures, you can try this:
var timeout = function(){
var i = 0;
return function(){
document.write(i+ ' ');
i++;
if(i!==5)
setTimeout(timeout,1000);
};
}();
setTimeout(timeout,1000);
Here is the example in jsBin http://jsbin.com/uloyuc/edit
First of all, NEVER pass a string to setTimeout. Use a function, it's much cleaner.
Second, you have to "close over" the loop value. I bet this is what you want.
for(var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
document.write(i + ' ')
}, i * 1000);
}(i));
}
See more about you a self executing function to close over a loop value here http://www.mennovanslooten.nl/blog/post/62
And just cause I love it, here is the equivalent in CoffeeScript whihc has the do keyword to help out with just this case.
for i in [0..4]
do (i) ->
setTimeout ->
document.write "#{ i } "
, i * 1000
You can also work with setInterval and clearInterval:
var i = 0;
var f = setInterval(function() {
if(i == 4) clearInterval(f);
document.write(++i + ' ');
}, 1000);
I think this code is very readable.
You could try like this:
var tick_limit = 5; // Or any number you wish representing the number of ticks
var counter = 0; // Or any number you wish
var timer_interval = 1000; // Interval for the counter
var timer;
function timerTick()
{
if(counter < tick_limit)
{
// Execute code and increase current count
document.body.innerHTML+=(counter + ' '); // Append the counter value to the body of the HTML page
counter++;
timer = setTimeout(timerTick,timer_interval);
}
else
{
// Reset everything
clearTimeout(timer);
counter = 0;
}
}
function startCounter()
{
clearTimeout(timer); // Stop current timer
timer = setTimeout(timerTick,timer_interval); // Start timer with any interval you wish
}
...
// Start timer when required
startCounter();
...
This way, calling the startCounter a number of times will result in a single timer executing the code
You're triggering five timeouts at the same time.
I like Pindatjuh's answer, but here's another fun way to do it.
This way starts the next timeout when the previous one is finished:
// Wrap everything in a self executing anonymous function so we don't pollute
// the global namespace.
//
// Note: Always use "var" statments or you will pollute the global namespace!
// For example "for(i = 0; i < 5; i++)" will pollute the global namespace
// unless you have "var i; for(i = 0; i < 5; i++)" or
// "for(var i = 0; i < 5; i++)" & all of that is not in the global namespace.
//
(function() {
// "i" will be available within doThis()
// you could also pass "i" as an argument
var i = 0,
doThis = function() {
// setTimeout can take an anonymous function
// or a regular function. This is better than
// eval-ing a string.
setTimeout(function() {
document.write(i + ' ');
++i;
// Do the function again if necessary
if (i < 5) doThis();
}, 1000);
}
// Let's begin!
doThis();
})();
Working Example

Categories