Passing Functions to Settimeout in a Loop: Always the Last Value

Passing functions to setTimeout in a loop: always the last value?

This is the very frequently repeated "how do I use a loop variable in a closure" problem.

The canonical solution is to call a function which returns a function that's bound to the current value of the loop variable:

var strings = [ "hello", "world" ];
var delay = 1000;
for(var i=0;i<strings.length;i++) {
setTimeout(
(function(s) {
return function() {
alert(s);
}
})(strings[i]), delay);
delay += 1000;
}

The outer definition function(s) { ... } creates a new scope where s is bound to the current value of the supplied parameter - i.e. strings[i] - where it's available to the inner scope.

setTimeout in for-loop does not print consecutive values

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);

setTimeout pass parameter in for loop

You don't need .bind(). Either use let or const instead of var...

const $scope = {};$scope.type = ["st", "ct", "sc", "rm", "bg", "sf", "fl", "sq", "pr", "cr", "vl", "fd"];
for (let i = 0; i < $scope.type.length; i++) { setTimeout(function () { console.log("i", i);
// your code
}, i * 2000);}

JS: setTimeout always prints values from last iteration

In the specific case of setTimeout, you don't even need a closure. You can pass variables to the timeout function, as follows:

for (var x in data.property) {
for (var i = 0; i < data.property[x].values.length; i++) {
var one = data.property[x].values[i][0];
var two = data.property[x].values[i][1];

setTimeout(function(one, two) {
write(one, two);
}, delay, one, two);

delay += 1000;
}
}

setTimeout with Loop in JavaScript

This has to do with how scoping and hoisting is being treated in JavaScript.

What happens in your code is that the JS engine modifies your code to this:

var count;

for (count = 0; count < 3; count++) {
setTimeout(function() {
alert("Count = " + count);
}, 1000 * count);
}

And when setTimeout() is being run it will first look in it's own scope after count but it won't find it so then it'll start looking in the functions that closes (this is called closures) over the setTimeout function until it finds the var count statement, which will have the value 3 since loop will have finished before the first timeout function has been executed.

More code-ily explained your code actually looks like this:

//first iteration
var count = 0; //this is 1 because of count++ in your for loop.

for (count = 0; count < 3; count++) {
setTimeout(function() {
alert("Count = " + 1);
}, 1000 * 1);
}
count = count + 1; //count = 1

//second iteration
var count = 1;

for (count = 0; count < 3; count++) {
setTimeout(function() {
alert("Count = " + 2);
}, 1000 * 2);
}
count = count + 1; //count = 2

//third iteration
var count = 2;

for (count = 0; count < 3; count++) {
setTimeout(function() {
alert("Count = " + 3);
}, 1000 * 3);
}
count = count + 1; //count = 3

//after 1000 ms
window.setTimeout(alert(count));
//after 2000 ms
window.setTimeout(alert(count));
//after 3000 ms
window.setTimeout(alert(count));

Javascript: how to pass different object to setTimeout handlers created in a loop?

Your first approach is evaluating code at runtime. You are most likely right about why it's failing (elem is not in the scope in which the code is eval'd). Using any form of eval() (and setTimeout(string, ...) is a form of eval()) is a general bad idea in Javascript, it's much better to create a function as in your second approach.

To understand why your second approach is failing you need to understand scopes and specifically closures. When you create that function, it grabs a reference to the i variable from the fadeIn function's scope.

When you later run the function, it uses that reference to refer back to the i from fadeIn's scope. By the time this happens however, the loop is over so you'll forever just get i being whatever it was when that loop ended.

What you should do is re-engineer it so that instead of creating many setTimeouts at once (which is inefficient) you instead tell your setTimeout callback function to set the next Timeout (or you could use setInterval) and do the incrementing if your values inside that callback function.

Javascript function is using the last known parameters in loop?

You are declaring the map variable without the var keyword so it's being created in the global scope, so there is only one map variable that gets it's value over-written each loop.

for(var k=0;k<arr_altern.length;k++){
(function (k) {
my_div=create_div_interchange(arr[i],1,78,visited_bus,interchange_arr,arr_altern[k],null, my_interchange_array);

$('#results').append(my_div);
var x = 'animate' + div_id,
v = '#animater' + div_id,
map = create_map(div_id),
poly = retrieve_results_edges(bus_stops_visited, map),
strVar = '<span class="animate"><input type="button" id="' + x + '" name="animate" value="Animate" /><\/span>';

$(v).append(strVar);

document.getElementById(x).onclick=function test(){

animate(poly,map);

}

set_map(map);

set_polyline_color(my_traversed_edge,map);
})(k);
}

Running your code inside of an IIFE (Immediately-Invoked Function Expression) will create a new scope for the code within it. This allows you to declare variables at the time of running and they will hold their value into the future (for instance they will be available to event handlers that fire in the future). Notice I used the var keyword when declaring all the variables so they are created in the current scope.

Update

You could also use $.each() to scope your code:

$.each(arr_altern, function (k, val) {
my_div=create_div_interchange(arr[i],1,78,visited_bus,interchange_arr,arr_altern[k],null, my_interchange_array);

$('#results').append(my_div);
var x = 'animate' + div_id,
v = '#animater' + div_id,
map = create_map(div_id),
poly = retrieve_results_edges(bus_stops_visited, map),
strVar = '<span class="animate"><input type="button" id="' + x + '" name="animate" value="Animate" /><\/span>';

$(v).append(strVar);

document.getElementById(x).onclick=function test(){

animate(poly,map);

}

set_map(map);

set_polyline_color(my_traversed_edge,map);
});


Related Topics



Leave a reply



Submit