Calling a function inside a loop breaks it - javascript

I have a function that loops an object from a MongoDB collection. It's all possible connections for some mail transportation posts. Once I get one connection, I want to immediately remove the inverse connection from the connections object, for example, postA=1 and postB=2, I want to remove postA=2 and postB=1 (removeConnection function does that).
I can't understand why it only returns one 'A' on the console when I try to run that function inside calculateRoute, and returns three 'A' (which is what it should) when I remove it. That function is somehow breaking the loop.
calculatedRoutes = calculateRoute(store.postid, client.postid, connections, []);
function calculateRoute(actualPost, finalPost, connections, routes) {
for(i=0; i < connections.length; i++) {
if(actualPost == connections[i].postA) {
console.log('A');
// If I remove this, the console shows A three times. If I keep this, only shows 1 time.
connections = removeConnection(connections[i].postB, connections[i].postA, connections);
}
}
return routes;
}
function removeConnection(postA, postB, connections) {
for(i=0; i < connections.length; i++) {
if(connections[i].postA == postA && connections[i].postB == postB) {
delete connections[i];
//break;
}
}
return connections;
}

It appears that you are modifying the collection that you are iterating over when you callremoveConnection. I would venture to say that after the first loop, connections.length is less than your loop control variable, which would cause the loop to terminate. What are the contents of connections after the function call?
In general, directly modifying a collection you're iterating over is bad practice. A better option would be to project the collection into a new one that contains the values you want (using map,filter,etc). That way your not mutating anything.

Fixed it by adding var to i=0 on each for. Can someone explain?
for(var i=0; i < connections.length; i++) {...

Related

javascript garbage collector not collecting setTimeout()s?

I have a function:
function test()
{
for( var i = 0; i < 1000000; i++ )
{
setTimeout( function()
{
//
}, 10000 );
}
}
Running this on chrome, it propels memory usage from around 50MB to 600MB, which I guess is ok; but after the timeouts have been executed, the garbage collector doesn't seem to remove them from memory, and it just stays at 600MB until I refresh, even then though it leaves some sort of "foot print" of 150MB after the page refresh.
Any idea how to tell the garbage collector to get rid of them after their execution?
You are correct that there appears to be memory that is never cleaned up. My best guess at the problem is that creating a function in a for loop like this creates a new scope which must have access to i. Therefore, those functions never get cleaned. I misspoke - the functions should definitely get cleaned up. Testing in my browser took the memory up over 900MB.
It's important to note that there is no benefit to doing what you are doing and it can be classified as "poor code" at best. You should create one function and reuse it:
// reusing a function fixes the problem
function test () {
var fn = function () {};
for (var i = 0; i < 1000000; i++) {
setTimeout(fn, 1000);
}
}
My observation was that memory usage shot back up over 900MB and then gradually fell back down to closer to normal over the course of a couple minutes.
If you are needing access to the variable i inside of your functions, I hate to say that your code will not give you that. You will only ever see the last value of i (1000000). If you are wanting to use i inside your functions, you can use a factory function. In my tests the memory eventually got cleaned up:
// using a factory fixes the problem and give you access to 'i'
function test () {
function factory (n) {
return function () {
/* This will get called later. 'n' will represent
the value of 'i' at the time this function was created */
}
}
for (var i = 0; i < 1000000; i++) {
setTimeout(factory(i), 1000);
}
};
Unfortunately, the memory problem persists if you use bind:
// Memory problem still exists with .bind
function test () {
var fn = function (n) { };
for (var i = 0; i < 1000000; i++) {
setTimeout(fn.bind(null, i), 1000);
}
}
As for the memory usage remaining high after a page refresh, this I cannot explain but it might have to do with the browser setting aside more memory for a tab which appears to be using a lot of memory. I dunno, just a guess.

Clicking objects on the evaluated page using CasperJS

I'm trying to click x number of objects on the evaluated page but it's failing after all day sunday attempts.. I have the following code, you can see I build up a list of objects in my variable (called itemsToAdd) and then I need that to be passed onto the evaluated page and then those objects to be clicked.
I know you can't pass complex objects to the evaluated page but every attempt I've tried has failed.. I've tried everything, please help. I've also tried custom js files although I couldn't get that working either.
$(function(ns) {
ns.myMethod = function(itemArray) {
var items = itemArray.items;
for (i = 0; i < items.length; i++) {
casper.thenEvaluate(function() {
casper.click(items[i].buttonId);
casper.waitUntilVisible(items[i].basketId, function() {
casper.echo(items[i].successMessage);
});
});
}
return this;
};
})(namespace('test'));
This is my variable, the buttonId is the DOM id for the button on the evaluated page. The basketId is another section on the evaluated page that gets updated to represent the button clicking has worked.
Complex variable
var itemsToAdd = {
'items': [
{
buttonId: '#button1',
basketId: '#nowInBasket1',
successMessage: 'It worked'
},
{
buttonId: '#button2',
basketId: '#nowInBasket2',
successMessage: 'this worked aswell'
}
]
};
Calling the code
test.myMethod(itemsToAdd);
There are multiple problems with your code.
evaluate is the sandboxed page context. There are no CasperJS or PhantomJS functions accessible inside of it. But it doesn't look like you are using the page context, so you should change thenEvaluate to then. I have written post here which shows for which things you can use the evaluate function/page context.
JavaScript has function scope. Read this question to learn more. This means that after the loop has executed all is point to the last i. You need a closure to fix this (here I use an IIFE, but you can also change the loop to itemArray.items.forEach(...).
var items = itemArray.items;
for (i = 0; i < items.length; i++) {
(function(i){
casper.then(function() {
casper.click(items[i].buttonId);
casper.waitUntilVisible(items[i].basketId, function() {
casper.echo(items[i].successMessage);
});
});
})(i);
}
If this doesn't solve your problem then this is probably a problem with your $ framework, whatever that is.

Convert for loop to recursive function

I've got a for loop, and would like to convert it into a recursive function.
I have a prompt that should pop up for every item and be dismissed before moving on to the next item in the list. This behavior doesn't seem to work with a normal for loop. The loop finishes before the prompt is even displayed, and then only one prompt shows up, even if there is more than one item in the list for which the prompt should have been displayed.
(I'm not totally sure if it will work with a recursive function, but from what I've read it seems promising. If I'm wrong about this there's no point in doing the conversion.)
I've been looking at other examples, but I can't seem to wrap my head around how exactly they work.
Here's my original loop:
for(var i = 0;i < items.get_count();i++) {
var item = items.getItemAtIndex(i);
//Is the item we're looking at in need of approval?
if (item.get_item("_ModerationStatus") === 2)
{
//Yes, prompt for approval. (code for prompt goes here.)
//Wait until prompt has received a response before going on next list item.
}
else
{
//No, do nothing.
}
}
My attempt looks a bit sad. I'm not really sure where to go from here:
function recursiveCheckRequests(i)
{
if (i < items.get_count())
{
function checkRequest(items, )
//???
}
}
recursiveCheckRequests(0);
You have to call the function from inside itself.
function recursiveCheckRequests(i) {
var item = items.getItemAtIndex(i);
//Is the item we're looking at in need of approval?
if (item.get_item("_ModerationStatus") === 2) {
//Yes, prompt for approval. (code for prompt goes here.)
//Wait until prompt has received a response before going on next list item.
}
if (i + 1 < items.get_count()) {
recursiveCheckRequests(i + 1);
}
}
recursiveCheckRequests(0);
this is what i would do
function recursiveCheckRequests(pos,arr,length)
{
if (pos === length){
return true;
}
//do something here with the array
return recursiveCheckRequests(pos+1,length)
}
recursiveCheckRequests(0,array,array.length);
this way the function is completely independent of the array to be passed and tyou can specify the limit on the iterations to perform.

JavaScript callback function with relative variables

I am not entirely sure how to phrase this question, but basically, I have a class, button that on its click should call the function passed to it.
button = function(...,callBack) {
//...
this._cb = callBack;
}
button.prototype.callBack = function(e) {
//...
this._cb();
}
and then somewhere else
//on canvas click
e.target.callBack(e);
(I hope this is about the right amount of background, I can give more if needed)
So the issue I am running into is when I dynamically instantiate the buttons such that their callbacks use data from an array. i.e.
for (var i = 0; i < levels.length; i++) {
buttons[buttons.length] = new button(..., function() {drawLevel(levels[i])});
}
Then when they are clicked, they run that callback code and try to find some random value for i (probably a for-loop that didn't use var) and runs that level.
My question is, how can I (without using eval) circumvent this problem.
Thanks!
I'm not 100% clear on what you're asking, but it looks like you're going to be getting the wrong value for i in the anonymous function you're creating in the loop (it will always be levels.length)
Way around this is to have a different scope for every function created, with the i in each scope being a copy of the i in the loop
buttons[buttons.length] = new button(..., (function(i){
return function() {drawLevel(levels[i])};
})(i));

Using this keyword within Socket.io on function

I'm using a socket.io listener within one of my functions to listen for a "loser" event to tell the client that the other client won. However, I can't use the "this" keyword to talk about my client while inside the socket.on function as this refers to the socket itself. Am I going about this the wrong way? Or can access the client object some other way, like super?
socket.on('loser', function() {
//Remove all current objects then restart the game.
//THIS PART DOESN'T WORK, SINCE 'THIS' NO LONGER REFERS TO
//THE GAME OBJECT, BUT INSTEAD REFERENCES THE SOCKET LISTENER.
for(var i = 0; i < this.board.objects.length; i++)
{
this.board.remove(this.board.objects[i]);
}
//WORKS AS EXPECTED FROM HERE ON...
Game.setBoard(1, new TitleScreen(gameType,
"Loser!",
"Press Space to Play Again",
playGame));
});
Functions don't carry any information about the objects that reference them, you can use .bind() to bind the function to your object before you pass it:
socket.on('loser', function() {
//Remove all current objects then restart the game.
//THIS PART DOESN'T WORK, SINCE 'THIS' NO LONGER REFERS TO
//THE GAME OBJECT, BUT INSTEAD REFERENCES THE SOCKET LISTENER.
for (var i = 0; i < this.board.objects.length; i++) {
this.board.remove(this.board.objects[i]);
}
//WORKS AS EXPECTED FROM HERE ON...
Game.setBoard(1, new TitleScreen(gameType, "Loser!", "Press Space to Play Again",
playGame));
}.bind(this));
In browser-land the common way to do this is to set a variable like var that = this; before you enter the function, and then use that instead.
However, ECMAScript5 brought in bind(), which allows you to prevent the value of this being lost. In NodeJS of course, it's safe to use this (unlike in browser-land, where you have to support older browsers).
socket.on('loser', (function() {
//Remove all current objects then restart the game.
for (var i = 0; i < this.board.objects.length; i++) {
this.board.remove(this.board.objects[i]);
}
//WORKS AS EXPECTED FROM HERE ON...
Game.setBoard(1, new TitleScreen(gameType, "Loser!", "Press Space to Play Again", playGame));
}).bind(this));​
For more info, see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
Whats wrong with something like this?
var self = this;
socket.on('loser', (function() {
//Remove all current objects then restart the game.
for (var i = 0; i < self.board.objects.length; i++) {
self.board.remove(self.board.objects[i]);
}
}

Categories

Resources