Closures in Loop not responding to the usual maneuvers - javascript

At first blush, this looks like the same old 'closures in a loop' problem, but applying my usual solution is not actually solving the problem. Here's the code:
G.MultiToggle = function(each){
//data takes the form
//[{"data":(see Toggle), "onOpen":function(){}, "onClosed":function(){}},...]
this.children = [];
var which = null;
var toggles = [];
var that = this;
function makeOpenFn(j){
var info = each[j];
console.log(j);
return function(){
console.log(j);
info["onOpen"]();
if(which!=null){
toggles[which].close_up();
}
which = j;
};
};
function makeClosedFn(j){
var info = each[j];
console.log(j);
return function(){
console.log(j);
info["onClosed"]();
which = null;
};
};
function makeToggler(obj,opener,closer) {
return new G.Toggle(
obj.data,
opener,
closer
);
};
for(var i=0; i<each.length; i++){
var openFn = makeOpenFn(i);
var closedFn = makeClosedFn(i);
toggles[i] = makeToggler(each[i],openFn,closedFn);
toggles[i].close_up();
that.addChild(toggles[i]);
}
console.log(toggles);
}
G.MultiToggle.prototype = new createjs.Container();
The openFn and closedFn are used as event handlers by the toggle object later on. When they're invoked, they all spit out the results from i=2. I've tried moving the info variable declaration into the inner functions, and many other gymnastic permutations. I'm pulling my hair out, over here. Any help would be appreciated.
EDIT: Added more of the surrounding code for context.

It's the old closure in a loop problem. Only, you've missed a variable:
for(var i=0; i<each.length; i++){
openFn = makeOpenFn(i); //------ looks OK
closedFn = makeClosedFn(i); //-- looks OK
toggles[i] = function(){
return new G.Toggle(
each[i].data, //--------- AHA! closure in a loop!
openFn,
closedFn
);
}();
toggles[i].close_up();
that.addChild(toggles[i]);
}
You just need to break the closure to that i as well:
toggles[i] = function(new_i){
return new G.Toggle(
each[new_i].data,
openFn,
closedFn
);
}(i);
Or, if you prefer the style of the other functions:
function makeToggler(obj,opener,closer) {
return new G.Toggle(
obj.data,
opener,
closer
);
}
for(var i=0; i<each.length; i++){
openFn = makeOpenFn(i); //------ looks OK
closedFn = makeClosedFn(i); //-- looks OK
toggles[i] = makeToggler(each[i],openFn,closedFn);
toggles[i].close_up();
that.addChild(toggles[i]);
}

If you think there is problem inside the loop, I'll show you that the problem is not from your loop.
Let's make a simple loop that same as your loop to proof it:
function my(a){
return a+3;
}
for (var i=0; i<3; i++){
var ab = my(i);
var aa = function(){
alert(ab);
alert(i);
}();
}

Related

Javascript - Removing array item & DOM

I'm trying to learn Javascript and so I made a little code, but there is something wrong with it that I can
var array = new Array("Apple","Mangosteen","Durian","Pineapples");
...
...
function B() {
...
...
var BP = $("<p></p>");
BP.text("Click \"x\" on items to be removed").append("<br/>");
for (i=0;i<array.length;i++) {
var f = array[i];
var F = $("<div></div>");
F.attr({"class":"f"});
var N = $("<span></span>");
N.attr({"class":"n"});
N.text(f);
var d = $("<span></span>");
d.attr({"class":"cc sl"});
d.bind("click", function(e){
e.stopPropagation();
e.preventDefault();
IR(f,F);
});
d.html("×");
...
...
}
function IR(f,F) {
var a = array.indexOf(f);
array.splice(a,1);
F.remove();
}
When I added console.log(f); in function IR(), the value passed will always be "Pineapples", regardless if I'm clicking "x" on "Apples" or "Durian", the f value passed will always be "Pineapples". What is wrong with it?
You call a function inside a for loop - something you can read more about here JavaScript closure inside loops – simple practical example.
For now, if you're using ES6. The easiest way to solve it will be using let in the for loop.
for (let i=0;i<array.length;i++) { ...... }
Or else, use Array.forEach() instead of the for loop.
In your case it should be something like
array.forEach(function(fruit) {
var f = fruit;
var F = $("<div></div>");
F.attr({"class":"f"});
var N = $("<span></span>");
N.attr({"class":"n"});
N.text(c);
var d = $("<span></span>");
d.attr({"class":"cc sl"});
d.bind("click", function(e){
e.stopPropagation();
e.preventDefault();
IR(f,F);
});
d.html("×");
...
...
});
its because the scope of variable "i" is global, so var f = array[i]; will result in var f = array[3]; so you will get only "Pineapples".
I will give you a simple sample code to understand the issue. please run below code.
<script>
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
</script>
you will get only 3 because "i" is in global scope.
When you declare a variable using var it will be declared within a scope of the function. That's why when you click 'x' javascript will pass to the IR function the last value of the variables - "Pineapples" and Pineapples's div. If you want to declare a variable within the 'for' cycle scope, use let. In this case, in every loop of 'for' cycle javascript will create a new variables f and F.
var array = new Array("Apple","Mangosteen","Durian","Pineapples");
function B() {
var BP = $("<div></div>");
BP.text("Click \"x\" on items to be removed").append("<br/>");
for (var i=0;i<array.length;i++) {
let f = array[i];
let F = $("<div></div>");
F.attr({"class":"f"});
var N = $("<span></span>");
N.attr({"class":"n"});
N.text(f);
var d = $("<span></span>");
d.attr({"class":"cc sl"});
d.bind("click", function(e){
e.stopPropagation();
e.preventDefault();
IR(f,F);
});
d.html("×");
F.append(N).append(d)
BP.append(F)
}
}
function IR(f,F) {
var a = array.indexOf(f);
array.splice(a,1);
F.remove();
}

Type error primeFactors.for is not a function

I've got a 'TypeError: primeFactors.for is not a function' error from Jasmine when I try to run this code, I've run into this type of error so many times. What's the best way to resolve it? I know that clearly .for is not a function but I'm not sure why?
var primeFactors = function(){};
primeFactors.prototype.for = function(num){
var array = [];
for(var i = 2; i < Math.ceil(Math.sqrt(num)); i++){
if(num % i === 0){
array.push(i);
num = num/i;
i--;
}
}
return array;
};
module.exports = primeFactors;
When you want to call .for, you must create an object instance for that prototype, for instance with the new keyword:
This is not OK:
primeFactors.for(3);
But this is OK:
var obj = new primeFactors;
obj.for(3);
For your Jasmine test, it would work like this:
it('returns an empty array for 1', function() {
expect((new primeFactors).for(1)).toEqual([]);
});
In case the tests are correctly written you can use static functions (inside classes) to pass them.
Take a look at:
https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Classes/static
"use strict";
class primeFactors {
static for(num) {
var array = [];
for(var i = 2; i < Math.ceil(Math.sqrt(num)); i++){
if(num % i === 0){
array.push(i);
num = num/i;
i--;
}
}
return array;
}
};
module.exports = primeFactors;

nodejs - pass global variable to promise callback in parse

consider this code
for (var i = 0; i < data.length; i++){
query.equalTo("objectId",data[i].id).first().then(
function(object){
object.set("username", data[i].username);
object.save();
}
);
}
in this example data[i] inside the then callback is the last element of the array
consider this 2nd example that normally work in javascript world
assume we use some API which connect to mongodb and has function called update
for (var i = 0; i < data.length; i++){
query.eq("_id",data[i].id).update(data[i].username);
}
eq returns the object, update updates that object and save it.
will it not be awesome if something like this is possible ... (not sure if it will also even work)
for (var i = 0; i < data.length; i++){
query.equalTo("objectId",data[i].id).first().then(
function(object, data[i]){
object.set("username", data.username);
object.save();
}
);
}
This actually doesn't work only because of scoping var. You can get the sample running as desired just by:
a) using let instead of var
b) creating a new scope for i (this is what let basically does). (In the anonymous fn, I used ii instead of i just for clarity. i would also work):
for (var i = 0; i < data.length; i++){
(function(ii) {
query.equalTo("objectId",data[ii].id).first().then(
function(object){
object.set("username", data[ii].username);
object.save();
}
);
})(i)
}
the best way to solve this problem with parse is to use recursivity
...
var do = function(entries, i){
if (entries[i]){
let user = data[i];
query.equalTo("objectId", user.id).first().then(
function(object){
object.set("username", user.username);
object.save();
}
).then(
function(){
return do(entries, i + 1);
}
);
}
}

Javascript: Using changing global variables in setTimeout

I am working on Javascript and using firefox scratchpad for executing it. I have a global index which I want to fetch inside my setTimeout (or any function executed asynchronously). I can't use Array.push as the order of data must remain as if it is executed sequentially. Here is my code:-
function Demo() {
this.arr = [];
this.counter = 0;
this.setMember = function() {
var self = this;
for(; this.counter < 10; this.counter++){
var index = this.counter;
setTimeout(function(){
self.arr[index] = 'I am John!';
}, 100);
}
};
this.logMember = function() {
console.log(this.arr);
};
}
var d = new Demo();
d.setMember();
setTimeout(function(){
d.logMember();
}, 1000);
Here, I wanted my d.arr to have 0 - 9 indexes, all having 'I am John!', but only 9th index is having 'I am John!'. I thought, saving this.counter into index local variable will take a snapshot of this.counter. Can anybody please help me understand whats wrong with my code?
The problem in this case has to do with scoping in JS.
Since there is no block scope, it's basically equivalent to
this.setMember = function() {
var self = this;
var index;
for(; this.counter < 10; this.counter++){
index = this.counter;
setTimeout(function(){
self.arr[index] = 'I am John!';
}, 100);
}
};
Of course, since the assignment is asynchronous, the loop will run to completion, setting index to 9. Then the function will execute 10 times after 100ms.
There are several ways you can do this:
IIFE (Immediately invoked function expression) + closure
this.setMember = function() {
var self = this;
var index;
for(; this.counter < 10; this.counter++){
index = this.counter;
setTimeout((function (i) {
return function(){
self.arr[i] = 'I am John!';
}
})(index), 100);
}
};
Here we create an anonymous function, immediately call it with the index, which then returns a function which will do the assignment. The current value of index is saved as i in the closure scope and the assignment is correct
Similar to 1 but using a separate method
this.createAssignmentCallback = function (index) {
var self = this;
return function () {
self.arr[index] = 'I am John!';
};
};
this.setMember = function() {
var self = this;
var index;
for(; this.counter < 10; this.counter++){
index = this.counter;
setTimeout(this.createAssignmentCallback(index), 100);
}
};
Using Function.prototype.bind
this.setMember = function() {
for(; this.counter < 10; this.counter++){
setTimeout(function(i){
this.arr[i] = 'I am John!';
}.bind(this, this.counter), 100);
}
};
Since all we care about is getting the right kind of i into the function, we can make use of the second argument of bind, which partially applies a function to make sure it will be called with the current index later. We can also get rid of the self = this line since we can directly bind the this value of the function called. We can of course also get rid of the index variable and use this.counter directly, making it even more concise.
Personally I think the third solution is the best.
It's short, elegant, and does exactly what we need.
Everything else is more a hack to accomplish things the language did not support at the time.
Since we have bind, there is no better way to solve this.
The setTimeout doesn't have a snapshot of index as you are expecting. All of the timeouts will see the index as the final iteration because your loop completes before the timeouts fire. You can wrap it in a closure and pass index in, which means the index in the closure is protected from any changes to the global index.
(function(index){
setTimeout(function(){
self.arr[index] = 'I am John!';
}, 100);
})(index);
The reason is that by the time settimeout is started, the for loop is finished executing the index value is 9 so all the timers are basically setting the arr[9].
The previous answer is correct but the source code provided is wrong, there is a mistyping elf in place of self . The solutions works.
An other way , without a closure , is to just add the index parameter to the function declaration in the setTimeout statement
function Demo() {
this.arr = new Array();
this.counter = 0;
this.setMember = function() {
var self = this;
for(; this.counter < 10; this.counter++){
var index = this.counter;
setTimeout(function(){
self.arr[index] = 'I am John!';
}(index), 100);
}
};
this.logMember = function() {
console.log(this.arr);
};
}
var d = new Demo();
d.setMember();
setTimeout(function(){
d.logMember();
}, 1000);

javascript using settimeout() with a loop

ive got a table with 8x10 cells.. each sell got an input element with its own id (11, 12, ... , 21,22,23,...)
now i want to fill these inputs after and after (lets say 0.5 sec)
i just entered some values for testing
Betrag = new Array();
Betrag[0] = new Array();
Betrag[1] = new Array("asd","asd","asd","asd","asd","asd","asd","asd","asd","asd","513.000,00");
Betrag[2] = new Array("asd","adsd","asd","asd","asd","asd","asd","asd","asd","asd","asd");
Betrag[3] = new Array("asd","asd","asd","asd","asd","asd","asd","asd","asd","asd","asd");
Betrag[4] = new Array("asd","uisgui","asd","asd","asd","asd","asd","asd","asd","asd","asd");
Betrag[5] = new Array("asd","asd","asd","asd","asd","asd","asd","asd","asd","asd","asd");
Betrag[6] = new Array("asd","asd","asd","asd","asd","asd","asd","asd","asd","asd","asd");
Betrag[7] = new Array("asd","asd","asd","asd","asd","asd","asd","asd","asd","asd","asd");
Betrag[8] = new Array("asd","asd","asd","asd","asd","asd","asd","asd","asd","asd","asd");
for(i=1; i<=8; i++){
for(k=1; k<=10; k++){
setTimeout(function schreiben(){document.getElementById(''+i+k+'').value= Betrag[i][k];}, 1000);
//document.getElementById(''+i+k+'').value= Betrag[i][k];
}
}
the compiler says "TypeError: Cannot read property '11' of undefined"
if i would not use the settimeout() function the whole loop is working fine, but with this function ive got this mistake..
You can try something like this:
var i = 1;
var k = 1;
var obj = setInterval( function () {
document.getElementById(i + '' + k).value= Betrag[i][k];
if(k <= 10)
k++;
else
{
k = 1;
if(i<=8)
i++;
else
clearInterval(obj);
}
}, 1000);
Here's a running example at http://jsfiddle.net/Ex98V/
This should work the way you wanted.
for(i=1; i<=8; i++){
for(k=1; k<=10; k++){
(function(i, k){
setTimeout(function schreiben(){document.getElementById(''+i+k+'').value= Betrag[i][k];}, 1000*k + 10000*i);
//document.getElementById(''+i+k+'').value= Betrag[i][k];
})(i, k);
}
}
To make things a bit clearer, consider refactoring like this :
for(i=1; i<=8; i++){
for(k=1; k<=10; k++){
setSchreibTimeout(i, k);
}
}
function setSchreibTimeout(i, k){
setTimeout(function schreiben(){document.getElementById(''+i+k+'').value= Betrag[i][k];}, 1000*k + 10000*i);
//document.getElementById(''+i+k+'').value= Betrag[i][k];
}
Bart Friederichs is right. Not sure why you want to do this that way, but you can declare a couple of vars inside the schreiben function and increment them inside the same screiben function.
k and i are read after the for loop ended (1 second to be precise). Their values are then 9 and 11, leading to an array overflow problem.
One option to fix it, is to create a function that does the work, and create a fixed string from the k and i variables to call it:
function schreiben(__i, __k) {
document.getElementById(''+__i+__k+'').value= Betrag[__i][__k];
}
Then call the setTimeout like this:
setTimeout("schreiben("+i+","+k+")", 1000);
Not the most elegant way, but it works.

Categories