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
Shiny: Start the App with Hidden Tabs, with No Delay
How to Convert Special Utf-8 Chars to Their Iso-8859-1 Equivalent Using JavaScript
Multiple, Sequential Fetch() Promise
How Would You Overload the [] Operator in JavaScript
Swift - Converting JSON Date to Swift Compatible Date
Stop a Youtube Video with Jquery
Filter Array of Objects by Multiple Properties and Values
JavaScript Parseint() with Leading Zeros
Send a File as Multipart Through Xmlhttprequest
What Is an Exclamation Point in JavaScript
Is There a Jquery Autogrow Plugin for Text Fields
How to Listen for Keyboard Open/Close in JavaScript/Sencha
How Can D3.Transform Be Used in D3 V4
Get Element at Specified Position - JavaScript
How Does JavaScript Determine the Number of Digits to Produce When Formatting Floating-Point Values
How to Update Single Value Inside Specific Array Item in Redux