I am learning about closures. This example is given as a common mistake made when making a closure:
function assignTorpedo(name, passengerArray) {
var torpedoAssignment;
for (var i = 0; i<passengerArray.length; i++) {
if (passengerArray[i] == name) {
torpedoAssignment = function() {
alert("Ahoy, " + name + "!\n" +
"Man your post at Torpedo #" + (i+1) + "!");
};
}
}
return torpedoAssignment;
}
Since the for loop completes before the closure is returned, the i value will not match with the name. So, I understand that the loop continues on before the return happens.
My question comes from this, an example of the correct way to do things:
function makeTorpedoAssigner(passengerArray) {
return function (name) {
for (var i = 0; i<passengerArray.length; i++) {
if (passengerArray[i] == name) {
alert("Ahoy, " + name + "!\n" +
"Man your post at Torpedo #" + (i+1) + "!");
}
}
};
}
I don't understand why in the above example the for loop wouldn't also continue past the first time it finds a match, which would result in another mismatched i. I understand that return stops a function, but I don't understand the connection between the return and that first match since they don't happen together (visually). I understand how the code knew to stop if that return was within the if function or the for loop.
I don't understand why in the above example the for loop wouldn't also continue past the first time it finds a match
It would.
which would result in another mismatched i.
It wouldn’t, because it checks if (passengerArray[i] == name) every time. That’s wasteful, though; it’s an unusual fix. A better way would be to pass the index:
function makeTorpedoAssigner(passengerArray, i) {
return function (name) {
alert("Ahoy, " + name + "!\n" +
"Man your post at Torpedo #" + (i+1) + "!");
};
}
function assignTorpedo(name, passengerArray) {
for (var i = 0; i<passengerArray.length; i++) {
if (passengerArray[i] == name) {
return makeTorpedoAssigner(passengerArray, i);
}
}
}
What happens here is :
assignTorpedo() returns a function based on name, So, every time it
checks for name in passengerArray and returns a function, but before
assignTorpedo could return torpedoAssignment, value of i would have
changed to the last value (length-1 of passengerArray), as loop will continue executing.
function assignTorpedo(name, passengerArray) {
var torpedoAssignment;
for (var i = 0; i<passengerArray.length; i++) {
if (passengerArray[i] == name) {
torpedoAssignment = function() {
alert("Ahoy, " + name + "!\n" +
"Man your post at Torpedo #" + (i+1) + "!");
// value of i
};
}
}
// value of i = length of Array since loop has executed fully
return torpedoAssignment;
}
Right approach explained :
Here you are returning a function which takes a name and checks each
time in the array, the concept of closure here is that, even though
function(name) is returned, it would remember passengerArray (if you
will see passengerArray is not passed everytime, but no error is
thrown. This is closure.)
function makeTorpedoAssigner(passengerArray) {
return function (name) {
for (var i = 0; i<passengerArray.length; i++) {
if (passengerArray[i] == name) {
alert("Ahoy, " + name + "!\n" +
"Man your post at Torpedo #" + (i+1) + "!");
//value of i
}
}
};
}
Related
Here it seems the for cycle executes the console.log(" floors["+i+"]: " + floor.floorNum()) line only, cycles through all 5 elements without calling the rest of the code, and after it finishes, only then the floor.on("up_button_pressed", function() is called.
What is exactly happening, so I can fix it?
Source: https://play.elevatorsaga.com/#challenge=2
{
init: function(elevators, floors) {
var elevator = elevators[0]; // Let's use the first elevator
// Whenever the elevator is idle (has no more queued destinations) ...
elevator.on("idle", function() {
elevator.goToFloor(2);
console.log("goToFloor 2 (because idle)")
});
elevator.on("floor_button_pressed", function(floorNum) {
elevator.goToFloor(floorNum);
console.log("goToFloor " + floorNum + " (because floor_button_pressed)")
} );
console.log("floors: " + floors)
for (i = 0; i < floors.length; i++) {
var floor = floors[i];
console.log(" floors["+i+"]: " + floor.floorNum())
floor.on("up_button_pressed", function() {
elevator.goToFloor(floor.floorNum());
console.log("goToFloor " + floor.floorNum() + " (because up_button_pressed)")
} );
floor.on("down_button_pressed", function() {
elevator.goToFloor(floor.floorNum());
console.log("goToFloor " + floor.floorNum() + " (because down_button_pressed)")
} );
}
},
update: function(dt, elevators, floors) {
// We normally don't need to do anything here
}
}
instead of var, use let because of JavaScript closure inside loops – simple practical example
I am trying to invoke a getJSON function for each value in an array and if the user is offline, display their name. However, I keep getting undefined. When a number is hard coded for i though, it works fine.
function getStreamersList() {
$('#listOfStreams').html("");
for (var i = 0; i < streamers.length; i++) {
console.log("This works: " + streamers[i]);
$.getJSON('https://api.twitch.tv/kraken/streams/' + streamers[i], function(data) {
if (data.stream !== null) {
$('#onlineStreams').append('<div class="online"><p>' + data.stream.channel.status + '</p></div>');
} else {
console.log(streamers[i]);
$('#offlineStreams').append('<div class="offline"><p>' + streamers[i] + ' is offline.</p></div>');
}
}, 'jsonp')
}
}
Can someone please explain what's happening? Thanks for any help!
I've been working on a script which collates the scores for a list of user from a website. One problem is though, I'm trying to load the next page in the while loop, but the function is not being loaded...
casper.then(function () {
var fs = require('fs');
json = require('usernames.json');
var length = json.username.length;
leaderboard = {};
for (var ii = 0; ii < length; ii++) {
var currentName = json.username[ii];
this.thenOpen("http://www.url.com?ul=" + currentName + "&sortdir=desc&sort=lastfound", function (id) {
return function () {
this.capture("Screenshots/" + json.username[id] + ".png");
if (!casper.exists(x("//*[contains(text(), 'That username does not exist in the system')]"))) {
if (casper.exists(x('//*[#id="ctl00_ContentBody_ResultsPanel"]/table[2]'))) {
this.thenEvaluate(tgsagc.tagNextLink);
tgsagc.cacheCount = 0;
tgsagc.
continue = true;
this.echo("------------ " + json.username[id] + " ------------");
while (tgsagc.
continue) {
this.then(function () {
this.evaluate(tgsagc.tagNextLink);
var findDates, pageNumber;
pageNumber = this.evaluate(tgsagc.pageNumber);
findDates = this.evaluate(tgsagc.getFindDates);
this.echo("Found " + findDates.length + " on page " + pageNumber);
tgsagc.checkFinds(findDates);
this.echo(tgsagc.cacheCount + " Caches for " + json.username[id]);
this.echo("Continue? " + tgsagc["continue"]);
this.click("#tgsagc-link-next");
});
}
leaderboard[json.username[id]] = tgsagc.cacheCount;
console.log("Final Count: " + leaderboard[json.username[id]]);
console.log(JSON.stringify(leaderboard));
} else {
this.echo("------------ " + json.username[id] + " ------------");
this.echo("0 Caches Found");
leaderboard[json.username[id]] = 0;
console.log(JSON.stringify(leaderboard));
}
} else {
this.echo("------------ " + json.username[id] + " ------------");
this.echo("No User found with that Username");
leaderboard[json.username[id]] = null;
console.log(JSON.stringify(leaderboard));
}
});
while (tgsagc.continue) {
this.then(function(){
this.evaluate(tgsagc.tagNextLink);
var findDates, pageNumber;
pageNumber = this.evaluate(tgsagc.pageNumber);
findDates = this.evaluate(tgsagc.getFindDates);
this.echo("Found " + findDates.length + " on page " + pageNumber);
tgsagc.checkFinds(findDates);
this.echo(tgsagc.cacheCount + " Caches for " + json.username[id]);
this.echo("Continue? " + tgsagc["continue"]);
return this.click("#tgsagc-link-next");
});
}
Ok, looking at this code I can suggest a couple of changes you should make:
I don't think you should be calling return from within your function within then(). This maybe terminating the function prematurely. Looking at the casperjs documentation, the examples don't return anything either.
Within your while loop, what sets "tgsagc.continue" to false?
Don't use "continue" as a variable name. It is a reserved word in Javascript used for terminating an iteration of a loop. In your case this shouldn't be a problem, but its bad practice anyhow.
Don't continually re-define the method within your call to the then() function. Refactor your code so that it is defined once elsewhere.
We ended up having to scope the function, so it loads the next page in the loop.
This is mainly because CasperJS is not designed to calculate scores, and it tries to asynchronously do the calculation, missing the required functions
This is the code that I currently have, one problem that is happening is I cannot use test() because presets[index].name and value are not visible outside of their function scope, how should I declare my array of objects in the global scope in order for me to be able to access these two variables in other functions?
var presets = [];
var index;
function CreatePresetArray(AMib, AVar) {
var parentpresetStringOID = snmp.getOID(AMib, AVar);
var presetStringOID = parentpresetStringOID;
parentpresetStringOID = parentpresetStringOID.substring(0, parentpresetStringOID.length - 2);
log.error("parentpresetStringOID is " + parentpresetStringOID);
var presetswitches = {};
for (var i = 1; i < 41; i++) {
presets.push(presetswitches);
try {
log.error("presetStringOID before getNextVB= " + presetStringOID);
vb = snmp.getNextVB(presetStringOID);
presetStringOID = vb.oid;
log.error("presetStringOID after getnextVB= " + presetStringOID);
var presetStringVal = snmp.get(presetStringOID);
log.error("presetStringVal= " + presetStringVal);
index = i - 1;
presets[index].name = presetStringOID;
presets[index].value = presetStringVal;
log.error("preset array's OID at position [" + index + "] is" + presets[index].name + " and the value stored is " + presets[index].value);
//log.error("presets Array value ["+index+"] = "+presets[index].configs);
if (presetStringOID.indexOf(parentpresetStringOID) != 0) {
break;
}
} catch (ie) {
log.error("couldn't load preset array " + index);
};
};
}
CreatePresetArray(presetMib, "presetString");
function test() {
for (i = 1; i < 41; i++) {
log.error("test" + presets[index].name + " " + presets[index].value);
};
}
test();
The for loop in your function test iterates over i but uses index inside the loop. Perhaps you meant to use
for (i = 0; i < 40; i++) { // 1 lower as you were using `index = i - 1` before
log.error("test" + presets[i].name + " " + presets[i].value);
}
Re-wrote your code. I don't think I made that much by way of change. If this doesn't clear up your problem, consider: Is the catch happening each iteration? Is the problem actually coming from a different method which is only visible here? Also, consider logging the whole presets Array when debugging to see what it looks like.
var presets = [];
function CreatePresetArray(AMib, AVar) {
var parentPresetOID, presetOID, presetValue, preset, vb, i;
parentPresetOID = snmp.getOID(AMib, AVar);
presetOID = parentPresetOID; // initial
parentPresetOID = parentPresetOID.substring(0, parentPresetOID.length - 2);
log.error("parentPresetOID is " + parentPresetOID);
presets = []; // empty array in case not already empty
for (i = 0; i < 40; ++i) {
try {
preset = {}; // new object
// new presetOID
vb = snmp.getNextVB(presetOID);
presetOID = vb.oid;
log.error("presetOID after getnextVB= " + presetOID);
// new value
presetValue = snmp.get(presetOID);
log.error("presetValue= " + presetValue);
// append data to object
preset.name = presetOID;
preset.value = presetValue;
// append object to array
presets.push(preset);
// more logging
log.error(
"preset array's OID at position [" + i + "]" +
" is" + presets[i].name + " and " +
"the value stored is " + presets[i].value
);
if (presetOID.indexOf(parentPresetOID) !== 0) {
break;
}
} catch (ie) {
log.error("couldn't load preset array " + i);
if (presets.length !== i + 1) { // enter dummy for failed item
presets.push(null);
}
}
}
}
Two options come to mind immediately:
you could pass the preset array as a argument to test().
You could put both CreatePresetArray() and test() inside a wrapper function and declare preset array at the top of your wrapper. That would give them both access to the variable.
It's generally considered Bad Form to declare globals if it can be avoided. Pollutes the namespace.
Attached is the code in question.
var http = require("http");
var i = 0;
var hostNames = ['www.1800autoland.com','www.youtube.com','www.1800contacts.com'];
for(i;i<hostNames.length;i++){
var options = {
host: hostNames[i],
path: '/'
};
http.get(options, function(res){
console.log("url: " + hostNames[i]);
console.log("status: " + res.statusCode);
for(var item in res.headers){
if(item == "server"){
console.log(item + ": " + res.headers[item]);
}
if(item == "x-powered-by"){
console.log(item + ": " + res.headers[item]);
}
if(item == "x-aspnet-version"){
console.log(item + ": " + res.headers[item]);
}
}
console.log("\n");
})
};
I have an array of URLs, and the issue I came to consult the site is that in my code, hostNames[i] does not display the n-th (or "i" in this case) index as a string. The output in console would always be "undefined." I have tried String(), toString(), and a number of different methods to no avail. Could someone point me to the right direction? What is the conversion I need to do?
This is a typical closure problem that occurs because of asynchronousness. When your callback fires the value of i will always be hostNames.length.
To fix it close around the value of i:
http.get(options, (function(res) { // '(' before function is optional, I use it to indicate immediate invocation
return function (i) { // this is the closed value for i
console.log("url: " + hostNames[i]);
console.log("status: " + res.statusCode);
// .. etc
};
}(i))); // immediate invocation with i
What's important to realize about using closures like this, is that you're making a number of anonymous functions, not just one. Each function is bound to its own value of i.
The easiest way to avoid having to write these strange bits of code is to not use for loops directly, but use a map function. Like:
function array_map(array, callback) {
var i, len = array.length;
for (i = 0; i < len; i += 1) {
callback(i, array[i]);
}
}
This makes it so you automatically close the value of i. Your loop will look like:
array_map(hostNames, function(i, hostname) { // i and hostname have the closed value
// .. etc
});
It's the closure problem, try this:
(function (i) {
http.get(options, function(res){
console.log("url: " + hostNames[i]);
console.log("status: " + res.statusCode);
for(var item in res.headers){
if(item == "server"){
console.log(item + ": " + res.headers[item]);
}
if(item == "x-powered-by"){
console.log(item + ": " + res.headers[item]);
}
if(item == "x-aspnet-version"){
console.log(item + ": " + res.headers[item]);
}
}
console.log("\n");
})
})(i);
You should use the .get(i) method to retrieve the item. You do not need to initialize the counter in the array, as others have stated.