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.
Related
I am proxying the function console.log to add some information to my logs and I am as well checking whether the information being logged is an object. I do this to avoid getting a log entry of the sort
2016-12-17 (22:12:51) > [object Object]
Code works fine when passing arguments that are not objects. For example, the command
console.log("hello","world");
prints
2016-12-17 (22:23:53) > hello
2016-12-17 (22:23:53) > world
But if I pass an object as well, the code will fail to insert a new line after the object. For example, the command
console.log("hello",{world:true,hello:{amount:1,text:"hello"}},"world");
prints
2016-12-17 (22:27:32) > hello
2016-12-17 (22:27:32) > { world: true, hello: { amount: 1, text: hello } } 2016-12-17 (22:27:33) > world
(note the missing line break after displaying the object).
Code
JQuery 3.1.1
main.js:
(function (proxied) {
function displayArg(argument){
var result= "";
if(typeof argument == "object") {
result += "{ ";
for (i in argument) {
result += i + ": ";
result += (displayArg(argument[i]));
result += ", "
}
result = result.substring(0,result.length - 2);
result += " }";
return result;
} else {
return argument;
}
}
console.log = function () {
var result = [];
for (i in arguments) {
var d = new Date();
result[i] = d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate() +
" (" + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds() + ") > ";
result[i] += displayArg(arguments[i]);
result[i] += "\n";
}
return proxied.apply(this, result);
}
})(console.log);
I'm not fully understanding objective but what about something along the lines of the following oversimplified override:
var oldLog = console.log;
console.log= function(){
var d= new Date(),
dateString = // process string
.....
for(var i = 0; i<arguments.length; i++){
oldLog(dateString, arguments[i]);
}
}
TL;DR change the iterator variables so they don't share name, or add a "var" to the loop definition to make sure they don't escape your desired scope.
It turns out that the for loops from (my own) console.log and displayArg were "sharing" the value of the iterator i. This is because by not declaring the iterator variable, the scope was broader than what I needed. To clarify, look at this example:
console.log({isThis:"real life"},"hello","world")
The code from console.log will add a date to the beginning of result[0] and then call displayArg(arguments[0]), arguments[0] being {isThis:"real life"}. That function, will iterate over the objects properties, thus i will be assigned the value isThis. After the function returns, the value of i will not go back to 0. Instead, i will be isThis and as a consequence, the line
result[i] += "\n";
translates to
result[isThis] += "\n"
instead of
result[0] += "\n"
Probably the most sensible solution was to add a var in the for declaration of the iterators. The following code works as expected:
(function (proxied) {
function displayArg(argument){
var result= "";
if(typeof argument == "object") {
result += "{ ";
for (var i in argument) {
result += i + ": ";
result += (displayArg(argument[i]));
result += ", "
}
result = result.substring(0,result.length - 2);
result += " }";
return result;
} else {
return argument;
}
}
console.log = function () {
var result = [];
for (var i in arguments) {
var d = new Date();
result[i] = d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate() +
" (" + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds() + ") > ";
result[i] += displayArg(arguments[i]);
result[i] += "\n";
}
return proxied.apply(this, result);
}
})(console.log);
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
}
}
};
}
bot.addListener('message', function (from, channel, message) {
IRClog.write("[" + (new Date()).toJSON() + "] [" + channel + "] <" + from + ">" + message + "\n");
// ============= PLAYER COMMANDS ============= //
if (logMessages){
util.log("[" + channel + "] <" + from + ">" + message);
}
console.log(message)// the message is logged
bot.whois(from,function(WHOIS){
if(typeof WHOIS.account == 'undefined'){
var isAuthed = false;
} else {
var isAuthed = true;
}
if (message.indexOf("!") === 0){//now the message is undefined
...
As described in the code, the var message is a string, and then, I don't know why, it becomes an undefined variable. Why is that happening? I didn't assign it to another value.
Depending on the execution context the function that the bot.whois executes may not have message defined in scope. You can use a closure to ensure the scope by passing in the message.
(function (msg) {
console.log(msg)// the message is logged
bot.whois(from, function(WHOIS){
var isAuthed = typeof WHOIS.account !== 'undefined';
if (msg.indexOf("!") === 0) {
...
}
})(message);
Your code is incomplete, obviously, and the actual bug probably resides somewhere below your cutoff point as you've put in your question:
bot.addListener('message', function (from, channel, message) {
IRClog.write("[" + (new Date()).toJSON() + "] [" + channel + "] <" + from + ">" + message + "\n");
// ============= PLAYER COMMANDS ============= //
if (logMessages){
util.log("[" + channel + "] <" + from + ">" + message);
}
console.log(message)// the message is logged
bot.whois(from,function(WHOIS){
if(typeof WHOIS.account == 'undefined'){
var isAuthed = false;
} else {
var isAuthed = true;
}
if (message.indexOf("!") === 0){//now the message is undefined
...
}); // end of the whois block, which is asynchronous
/* somewhere down here, message probably gets set to undefined
like this:
message = undefined; // this would run before bot.whois(from, cb); finishes
*/
}); // end of addListener
You either need to make sure that you're not messing with the message below your whois call, or you need to create a copy that you don't mess with, or you need to follow #Romoku's advice and wrap your whois call in a properly formatted closure (like the following) and pass message in to a strictly local scope:
bot.addListener('message', function (from, channel, message) {
IRClog.write("[" + (new Date()).toJSON() + "] [" + channel + "] <" + from + ">" + message + "\n");
// ============= PLAYER COMMANDS ============= //
if (logMessages){
util.log("[" + channel + "] <" + from + ">" + message);
}
console.log(message)// the message is logged
(function (msg) {
bot.whois(from,function(WHOIS){
if(typeof WHOIS.account == 'undefined'){
var isAuthed = false;
} else {
var isAuthed = true;
}
if (msg.indexOf("!") === 0){//now the message is undefined
...
}); // end of the whois block, which is asynchronous
})(message);
/* now you can do whatever you want to the message variable and bot.whois will
be able to operate on its independent, unmolested copy
*/
}); // end of addListener
Please note how in my example, and #Romoku's, message has been explicitly renamed (to msg and m, respectively), to make it clear that you're working with in a different scope with a different copy of the data.
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 just freakin weird to me. So if I don't
function BindAlbumAndPhotoData()
{
// Get an array of all the user's Albums
var aAlbums = GetAllAlbums(userID, token);
alert("aAlbums: " + aAlbums);
if (aAlbums == null || aAlbums == "undefined")
return;
// Set the default albumID
var defaultAlbumID = aAlbums[0].id;
};
So I get an undefined error on the line var defaultAlbumID = aAlbums[0].id; if I don't uncomment the alert("aAlbums: " + aAlbums);
what the heck? If I comment out alert("aAlbums: " + aAlbums); then I get an undefined for the var defaultAlbumID = aAlbums[0].id;
This is so weird. I've been working all night to figure out why I kept getting an undefined for the aAlbum[0] and as soon as I add back an alert that I used to have above it, all is fine...makes no sense to me.
Here's the full code of GetAllAlbums:
function GetAllAlbums(userID, accessToken)
{
var aAlbums = []; // array
var uri = "/" + userID + "/albums?access_token=" + accessToken;
alert("uri: " + uri);
FB.api(uri, function (response)
{
// check for a valid response
if (!response || response.error)
{
alert("error occured");
return;
}
for (var i = 0, l = response.data.length; i < l; i++)
{
alert("Album #: " + i + "\r\n" +
"response.data[i].id: " + response.data[i].id + "\r\n" +
"response.data[i].name: " + response.data[i].name + "\r\n" +
"response.data[i].count: " + response.data[i].count + "\r\n" +
"response.data[i].link: " + response.data[i].link
);
aAlbums[i] = new Album(
response.data[i].id,
response.data[i].name,
response.data[i].count,
response.data[i].link
);
alert("aAlbums[" + i + "].id : " + aAlbums[i].id);
}
});
return aAlbums;
}
so I'm not returning the array until I hit the callback of the FB.api async call so I don't see how my defaultAlbumID = aAlbums[0].id; line of code is executing before I have a valid array of data back. When I put in the alert, ovbvioulsly it's delaying before it hits my line defaultAlbumID = aAlbums[0].id; causing it to I guess luckily have data beacuse the async FB.api call is done but again I don't see how that's even possible to have an issue like this when I'm waiting for the call before proceeding on and returning the array to aAlbums in my BindAlbumAndPhotoData() method.
UPDATE #3
function BindAlbumAndPhotoData()
{
GetAllAlbums(userID, accessToken, function (aAlbums)
{
alert("we're back and should have data");
if (aAlbums === null || aAlbums === undefined) {
alert("array is empty");
return false;
}
var defaultAlbumID = aAlbums[0].id;
// Set the default albumID
var defaultAlbumID = aAlbums[0].id;
// Bind the album dropdown
alert(" defaultAlbumID: " + defaultAlbumID);
});
};
function GetAllAlbums(userID, accessToken, callbackFunctionSuccess)
{
var aAlbums = []; // array
var uri = "/" + userID + "/albums?access_token=" + accessToken;
FB.api(uri, function (response)
{
// check for a valid response
if (!response || response.error)
{
alert("error occured");
return;
}
for (var i = 0, l = response.data.length; i < l; i++)
{
alert("Album #: " + i + "\r\n" +
"response.data[i].id: " + response.data[i].id + "\r\n" +
"response.data[i].name: " + response.data[i].name + "\r\n" +
"response.data[i].count: " + response.data[i].count + "\r\n" +
"response.data[i].link: " + response.data[i].link
);
aAlbums[i] = new Album(
response.data[i].id,
response.data[i].name,
response.data[i].count,
response.data[i].link
);
alert("aAlbums[" + i + "].id : " + aAlbums[i].id);
}
// pass the array back to the callback function sent as a param to the GetAllAlbums method here
callbackFunctionSuccess(aAlbums);
});
}
It's not hitting my alert in the callback. I must still be doing something wrong here.
UPDATE #4 - for some reason it's not hitting my FB.api callback now.
function GetAllAlbums(userID, accessToken, callbackFunctionSuccess)
{
var aAlbums = []; // array
var uri = "/" + userID + "/albums?access_token=" + accessToken;
alert("uri: " + uri);
FB.api(uri, function (response)
{
// check for a valid response
if (!response || response.error)
{
alert("error occured");
return;
}
for (var i = 0, l = response.data.length; i < l; i++) {
alert("Album #: " + i + "\r\n" +
"response.data[i].id: " + response.data[i].id + "\r\n" +
"response.data[i].name: " + response.data[i].name + "\r\n" +
"response.data[i].count: " + response.data[i].count + "\r\n" +
"response.data[i].link: " + response.data[i].link
);
aAlbums[i] = new Album(
response.data[i].id,
response.data[i].name,
response.data[i].count,
response.data[i].link
);
alert("aAlbums[" + i + "].id : " + aAlbums[i].id);
}
alert("about to pass back the array to the callback function");
// pass the array back to the callback function sent as a param to the GetAllAlbums method here
callbackFunctionSuccess(aAlbums);
});
}
function BindAlbumAndPhotoData()
{
// Get an array of all the user's Albums
GetAllAlbums(userID, token, function(aAlbums){
// Set the default albumID
var defaultAlbumID = aAlbums[0].id;
});
};
and then in the GetAllAlbums function call the success function when you have the data back
//********* AFTER THE BREAK *******//
In response to the updated question: The FB API is mostly asynchronous, and will keep executing other code while it waits. So using your code, all I have done is passed in the function, and then call the function you've passed it at the end
function GetAllAlbums(userID, accessToken, funcSuccess)
{
var aAlbums = []; // array
var uri = "/" + userID + "/albums?access_token=" + accessToken;
alert("uri: " + uri);
FB.api(uri, function (response)
{
// check for a valid response
if (!response || response.error)
{
alert("error occured");
return;
}
for (var i = 0, l = response.data.length; i < l; i++)
{
alert("Album #: " + i + "\r\n" +
"response.data[i].id: " + response.data[i].id + "\r\n" +
"response.data[i].name: " + response.data[i].name + "\r\n" +
"response.data[i].count: " + response.data[i].count + "\r\n" +
"response.data[i].link: " + response.data[i].link
);
aAlbums[i] = new Album(
response.data[i].id,
response.data[i].name,
response.data[i].count,
response.data[i].link
);
alert("aAlbums[" + i + "].id : " + aAlbums[i].id);
}
funcSuccess(aAlbums);
});
}
Is your function GetAllAlbums() doing some HTTP requests? If so then you need to either make that call synchronous or you need to put your code into a function and pass that as a callback to the Ajax request.
Try three equals signs instead of two, and also... return false rather than nothing at all.
if (aAlbums === null || aAlbums === undefined)
return false;
Also, undefined doesn't need to be in quotes, otherwise, it's just considered a string with a value of "undefined"
On an added note, it's probably better to ALSO check if aAlbums is actually an array before you decide to return a key from it.
if ( aAlbums === null
|| aAlbums === undefined
|| (typeof(aAlbums)=='object'&& !(aAlbums instanceof Array))
} return false;
Try modifying your condition like this:
if (typeof aAlbums == 'undefined')
return;
Also make sure that aAlbums has values and is an array:
alert(aAlbums.length);
Or:
for(var i = 0; i < aAlbums.length; i++)
{
alert(aAlbums[i].id);
}