How to pass an argument present only at callback creation [duplicate] - javascript

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

Related

What's the use of 'cb => cb(snapshot)' in this code? [duplicate]

This question already has answers here:
What's the meaning of "=>" (an arrow formed from equals & greater than) in JavaScript?
(14 answers)
Closed 4 years ago.
What is the use of 'cb => cb(snapshot)' ?
if (this._snapshotCallbacks.length > 0) {
const snapshot = gl.canvas.toDataURL();
this._snapshotCallbacks.forEach(cb => cb(snapshot));
this._snapshotCallbacks = [];
}
requestSnapshot (callback) {
this._snapshotCallbacks.push(callback);
}
this._snapshotCallbacks.forEach(cb => cb(snapshot));
can be rewritten as
this._snapshotCallbacks.forEach(function (callback) {
callback(snapshot)
});
i think it's clear when there is no arrow function?
this._snapshotCallbacks.forEach(cb => cb(snapshot));
means there is a collection of callbacks and the code here invokes them one after each other.
forEach is a function that exists on the array prototype that will take a function as a parameter and invoke this function for each element in the collection. When the function is invoked, the element is passed as the sole argument to the function that forEach took cb => cb(snapshot) is an es6 shorthand function definition.
So in your example, cb is a callback thats then invoked with the snapshot.
Basically, this is the same idea in a forloop
var function1 = (msg) => { alert(msg);}
var function2 = (msg) => { alert(msg.toUpperCase());}
var functions = [function1, function2];
for(var i = 0; i < functions.length; i++){
var fnToApply = functions[i];
fnToApply("hello world");
}
in which an array of functions is invoked in a loop, and each function is designed ahead of time to know what it takes as a param

How to create event listeners using a for loop in JS [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
I am trying to create event listeners for a pure JS dropdown menu. When I try to create the listeners using a for loop I get an error and it doesn't work, but when I create them manually using arrays they work perfectly. What am I missing?
var dropDown = document.getElementsByClassName('nav-sub__mobile-dropdown');
var subNavList = document.getElementsByClassName('nav-sub__list');
for ( i = 0; i < dropDown.length; i++) {
dropDown[i].addEventListener('click', function() {
subNavList[i].classList.toggle('nav-sub__list--active');
});
}
The above doesn't work, but if I create the event listeners manually using the arrays it does work.
dropDown[0].addEventListener('click', function() {
subNavList[0].classList.toggle('nav-sub__list--active');
});
dropDown[1].addEventListener('click', function() {
subNavList[1].classList.toggle('nav-sub__list--active');
});
dropDown[2].addEventListener('click', function() {
subNavList[2].classList.toggle('nav-sub__list--active');
});
When I use the for loop I get the following error code in my console.
Uncaught TypeError: Cannot read property 'classList' of undefined
UPDATE SOLVED PROBLEM
I was able to solve the problem using let thanks to Ben McCormick's comment here:
JavaScript closure inside loops – simple practical example
The solution was to simply use let in the for loop.
for (let i = 0; i < dropDown.length; i++) {
Because when the click function is called, the variable i has the last value it held, dropDown.length, not the loop value at the time addEventListener was called.
What you want is something like this:
for ( i = 0; i < dropDown.length; i++) {
function addListener(n) {
dropDown[n].addEventListener('click', function() {
subNavList[n].classList.toggle('nav-sub__list--active');
});
}
addListener(i);
}

javascript callback : undefined global variable [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
I have a problem with this code.
var uls = document.getElementsByClassName("gal");
for (var i = 0; i < uls.length; i++) {
// impossible to pass uls[i] as callback argument (undefined in callback)
uls[i].addEventListener("click", function() { openSlideShowModal(uls[i].tagName);} );
}
I have the error : Uncaught TypeError: Cannot read property 'tagName' of undefined
I can't explained why uls[i] is undefined as it is global. I know that there's no use of it because it could be accessible by this in the callback. But i found this incomprehensible error by chance.
Moreover if i modify slightly i no longer have an error
var uls = document.getElementsByClassName("gal");
for (var i = 0; i < uls.length; i++) {
tagName = uls[i].tagName;
// impossible to pass uls[i] as callback argument (undefined in callback)
uls[i].addEventListener("click", function() { openSlideShowModal(tagName);} );
}
A bind this should help you out getting the function back into the same context where ever it is being called.
function() {
openSlideShowModal(uls[i].tagName);
}.bind(this)

Variable is lost when passed as a parameter in setTimeout() [duplicate]

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>

how can I get the value of a variable in a closure in javascript [duplicate]

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.

Categories