This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I'm trying to use a variable value in a callback function, but am unable to get the original value of the variable when written into the function.
for (i=0; i<2; i++) {
if ($.type(picker[i]) == "object") {
var id = picker[i].$root[0].id;
picker[i].on({
set: function() {
alert("#" + id + " .picker__holder");
},
});
// other code..
}
}
This code is supposed to go throught the picker object twice, where the id value is first "datepicker" and on the second run "datepicker--2" but when the callback is called the id is always the second value "datepicker--2" where for the picker[0].on() callback i'd need it to be the first one ("datepicker").
I assume this is because the value of the variable is read when the callback code is executed, and by that time it has the last value.
What would be a reasonable workaround for this?
Best,
Alari
You need to refactor the inner asynch call into a function.
for (i=0; i<2; i++) {
if ($.type(picker[i]) == "object") {
var id = picker[i].$root[0].id;
// other code..
asynhCall(picker, i, id);
}
}
function asynhCall(picker, i, id)
{
picker[i].on({
set: function() {
alert("#" + id + " .picker__holder");
},
});
}
This is because by the time asynch-callback handler was executed, only id will have only the last value would be used (since because of event-loop model).
Related
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 3 years ago.
I would like to pass an argument to a callback function inside a for loop. The problem is the callback is called after the for loop ends and then the parameter does not exist anymore.
See the following code snippet:
function foo(x){
console.log(x)
}
for(var i = 0; i < arr.length; i++) {
var myObj = customObject()
myObj.on('click', function(){
foo(arr[i])
}
}
myObj is clicked on after the for loop ends which triggers an error:
TypeError: arr[i] is undefined
Is there any way to force the argument to be passed by its value at time of binding the callback function?
Try an IIFE:
myObj.on('click', function() {
(e => foo(e))(arr[i]);
});
The problem was that the click event was firing after the whole loop had finished - so i was arr.length, had not passed the conditional statement in the loop, and as such arr[i] was undefined.
You'd also need to use let instead:
for (let i = 0; i < arr.length; i++) {
let myObj = createObject();
myObj.on("click", function() {
(e => foo(e))(arr[i]);
});
}
This question already has answers here:
Javascript infamous Loop issue? [duplicate]
(5 answers)
Closed 5 years ago.
I,
I know, there is a lot information about this on SO, but I didn't find the right answer for my situation.
I have this piece of code:
for(var i=0; i < shop.collections.length; i++){
if(!shop.collection[i].active){
var data = {
name: shop.collection[i].name,
visible: true
};
myOwnService.createCollection(data, accessToken, function(err, response, body){
shop.collections[i].serviceId = body.id;
})
}
My problem is that shop is undefined in the myOwnService.createCollection() service. What is the right way to access the shop variable in the callback, so how I can update the object so I can save the result?
shop is in the parent scope of the myOwnService.createCollection function hence it is accessible inside it.
Only issue in above code is i used in callback which will always be the last value as it will be executed after event loop.
Use IIFE wrapper around myOwnService.createCollection so that scope of i could be passed.
for (var i = 0; i < shop.collections.length; i++) {
if (!shop.collection[i].active) {
var data = {
name: shop.collection[i].name,
visible: true
};
(function(i) {
myOwnService.createCollection(data, accessToken, function(err, response, body) {
shop.collections[i].serviceId = body.id;
});
})(i);
}
}
It should be accessible inside where you try to access. If you are tying Chrome debugger to see if it has the value, Chrome debugger sometime mess up and shows it like is not available.
https://bugs.chromium.org/p/v8/issues/detail?id=3491
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I have a problem with calling a function with a parameter inside a setTimeout function. Basically I'm trying to make a small online game, where I create a queue of commands and then execute them one at a time (each takes some time to show a visualization).
Unfortunately it seems that I cannot pass any variable as a parameter inside the setTimeout(). Although the variable does exist when I call the function it does not exist later when it is executed. The function doesn't keep track of the passed value.
Is there any solution to this? Thanks a lot for any help. Here is a code I use:
function executeCommands() {
var commands = document.getElementsByClassName("cmdplace");
var timeout = 0;
for (i = 0; i < commands.length; i++) {
console.log(commands[i].childNodes[0]); //variable exists
setTimeout(function() {go(commands[i].childNodes[0]);}, timeout+=400); //Uncaught TypeError: Cannot read property 'childNodes' of undefined
console.log(commands[i].childNodes[0]); //variable still exists
}
}
function go(command) {
//do somethig based on the passed command
}
When your functions are invoked, i is equal to commands.length and commands[i] is undefined.
They are capturing the variable i, not its value.
When they execute, they get out of i the actual value, but so far it has reached commands.length (that is the condition used to break your loop).
You can do something like this to work around it:
setTimeout(function(j) {
go(commands[j].childNodes[0]);
}.bind(null, i), timeout+=400);
Or this:
setTimeout((function(j) {
return function() {
go(commands[j].childNodes[0]);
};
})(i), timeout+=400);
Note also that, as you defined it, i is a global variable.
As mentioned in the comments by #PMV, there's a much easier way in modern JavaScript (if that's an option for you).
Just use a let statement as it follows:
for (let i = 0; i < commands.length; i++) {
// do whatever you want here with i
}
This will ensure that each iteration gets a new variable named i and you can capture it as in the original code.
You need to make a distinct copy of each item. By the time the setTimeout runs the loop has already finished.
var timeout = 0;
function executeCommands() {
var commands = document.getElementsByClassName("cmdplace");
for (i = 0; i < commands.length; i++) {
go(commands[i]);
}
}
function go(command) {
setTimeout(function() {
console.log(command);
}, timeout += 400);
}
executeCommands();
<ul>
<li class="cmdplace">A</li>
<li class="cmdplace">B</li>
<li class="cmdplace">C</li>
<li class="cmdplace">D</li>
</ul>
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I'm creating an array of callbacks like this:
function createFunctions(n) {
var callbacks = [];
for (var i=0; i<n; i++) {
callbacks.push(function() {
return i;
});
}
return callbacks;
}
And, here's the test that I'm working with:
var callbacks = createFunctions(5);
for (var i=0; i<callbacks.length; i++) {
Test.assertEquals(callbacks[i](), i, 'Function with index ' + i);
}
Basically, I want the callback to return it's index in the array (i.e. callback[1]() should return 1). But, i defined in createFunctions is set to 6 when the test runs so they always return 6. I've tried creating a local variable to hold the value in the anonymous function but that's not working either.
Any ideas how to make this work?
A closure has an enduring reference to the variables it closes over, not a copy of their value as of when it was created. That's why all of your functions see 6: That's the value that i has as of when those functions are called.
If you need them to see different values, make the closures close over something else that doesn't change. Here's one way:
function createFunctions(n) {
var callbacks = [];
for (var i=0; i<n; i++) {
callbacks.push(makeCallback(i));
}
return callbacks;
function makeCallback(index) {
return function() {
return index;
};
}
}
Or:
function createFunctions(n) {
var callbacks = [];
for (var i=0; i<n; i++) {
callbacks.push(makeCallback(i));
}
return callbacks;
}
function makeCallback(index) {
return function() {
return index;
};
}
(In this example, it doesn't matter whether makeCallback is within createFunctions or outside it.)
Now, the callbacks close over the context containing the index argument. They each get their own copy of that context (which is created by calling the makeCallback function), and so since nothing ever changes index, its value remains unchanged.
More (on my blog): Closures are not complicated
This feature of closures closing over the context of the variable is very, very useful. Consider:
function allocator(start) {
return function() {
return ++start;
};
}
var f = allocator(0);
alert(f()); // "1"
alert(f()); // "2"
alert(f()); // "3"
That wouldn't work if the function created by allocator had a copy of the value of start. It works because the function created by allocator has a reference (through the context) to the variable itself.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript infamous Loop problem?
I have the following code:
function test() {
var columns = options.columns;
for (var i =0; i < columns.length; i++) {
if (columns[i].type === "number") {
var field = columns[i].field;
columns[i].footerTemplate = function(data) { return buildFooter(data, field); };
}
}
}
function buildFooter(data, field) {
alert(field);
}
A library function calls the footerTemplate function (which in turn call buildFooter). The alert in buildFooter states that field is always the same value (the last value iterated in for loop of test.) How can buildFooter be called with the appropriate field value (i.e.
columns[0].footerTemplate = function(data) { return buildFooter(data, columns[0].field);}
and
columns[1].footerTemplate = function(data) { return buildFooter(data, columns[1].field);}
and so on...
Javascript does not scope variables inside logical blocks (loops, ifs). That field variable is shared across all the footerTemplate properties.
You can solve this by creating an inline function to create some scope:
for (var i =0; i < columns.length; i++) {
if (columns[i].type === "number") {
(function () {
var field = columns[i].field;
columns[i].footerTemplate = function(data) {
return buildFooter(data, field);
};
})();
}
}
}
Try:
columns[i].footerTemplate = (function(field) {
return function (data) {
buildFooter(data, field);
};
})(field);
It immediately executes a function that returns a new function that has the correctly bound field variable. It's definitely a scope issue that is a problem with loops, so using an immediately invoked function to create a new scope and making the correct variables available. This is definitely a duplicate of Javascript infamous Loop issue? though
Javascript is function-scoped language, so your decleration of
field
variable inside of for loop is the same as you declared it outside of foor loop, you're just overwriting same variable over and over again, there's no memory allocated for field variable in every loop, it's one same memory space that's being written to.