Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript Node.js Basics (2014) Building a Command Line Application Perfecting: Getting Multiple Profiles

Hossam Khalifa
Hossam Khalifa
17,200 Points

Adding subjects to the command line app

I started implementing the feature of choosing the subject to get the points for.This was easy but my problem was in displaying the error message.The error message is displayed for every user.But I only want to log it out once.

Here is app.js

var profile = require("./profile.js")


var subj = process.argv[2]
var users = process.argv.slice(3);


users.forEach(function(currentValue){

  profile.get(currentValue,subj)

})

Here is profile.js

//Print out message
var http = require("http");

var subjs;
var profile;

var printMessage =  function(username,badgeCount,points,subj){
  var message = username + " has " + badgeCount + " total badges and "+ points+ " points in "+subj;
  if(points === undefined ){

    console.error("The subject:("+subj+") does not exist.Please choose a valid subject.Subjects are case senitive.Here is a subject list :"+ subjs);
    return false;






    } else{
  console.log(message);
}}

//Print out ERROR messages
function printError(error){
   console.error(error.message);
}

function get(username,subj){
//connect to API URL(https://teamtreehouse.com/username.json)
var req = http.get("http://teamtreehouse.com/"+username+".json",function(response){


  var body="";
  //Read the data from the response
   response.on('data', function (chunk) {
    body += chunk;
  });

  //Parse the data 
     response.on('end',function(){
      if(response.statusCode === 200){

       try { profile = JSON.parse(body); 
         subjs = Object.keys(profile.points).splice(1);
    //Print the data out

    printMessage(username,profile.badges.length,profile.points[subj],subj);

  } catch(error){
    //Parsing Error
      printError(error);
   }
      }else{
        //StatusCode Error
        printError({message:"There was an error getting the profile for "+ username +" ("+http.STATUS_CODES[response.statusCode]+")" +" please try again later"})
      } 
     });


  //conncetion Error
  req.on("error",printError)
})
}


module.exports.get = get;

I know why it is displaying twice but I can not find any solution to displaying only once.

1 Answer

Sean T. Unwin
Sean T. Unwin
28,690 Points

Good idea, Hossam Khalifa .

One way to implement the display of the error message only once is explained below. The re-factored files, formatted and with comments, are pasted after the explanation.

This will be 2 step process - Step 1 will concentrate on handling the users array that was passed to start the app and Step 2 will focus on the subject that was passed to start the app.

  1. Handling the users array

    1. Declare variables to handle unknown user(s) error
    2. Add a third parameter to get() function
    3. Refactor parsing portion of get()
    4. Create function to print unknown users error message
  2. Handling the subject

    1. Declare variables to handle unknown subject error
    2. Refactor printMessage()
    3. Refactor parsing portion of get() (again)

Step 1: Handling the users array

Declare variables to handle unknown user(s) error

  • In profile.js after var profile; and before the function declarations insert
// Used for status code error message output
// Array for users that were not found
var unknownUsers = [];
// Counter for users not returned
var counter = 0;

Add a third parameter to get() function

  • In app.js change profile.get(currentValue,subj) by adding a third variable (users.length) to the function call so it looks like:
profile.get(currentValue, subj, users.length)
  • In profile.js change the get() function declaration, in order to handle the third parameter, to:
// Add third parameter of numUsers
function get(username,subj, numUsers) {

Refactor parsing portion of get() in profile.js

  • Immediately following the line, response.on('end',function() { insert
// Increase the counter as we loop through the users array
counter ++;
  • Within the try block, after the printMessage() function call, insert
// Print error message of unknown/ not found users if there are any
printUnknownUsersError(counter, numUsers);
  • Within the else portion of the status code if statement replace the printError() function call with
// Add falsey username to array
unknownUsers.push(username);
// Print error message of unknown/ not found users if there are any
// We need to call this here in case all users were not found
printUnknownUsersError(counter, numUsers);

Create function to print unknown users error message

  • In profile.js above the get() function declaration add the following function:
function printUnknownUsersError(counter, numUsers) {
  // At the last item of users and at least 1 unknown user esixts
  if (counter === numUsers && unknownUsers.length > 0) {
    var message =  "\n"; // newline for spacing before this error message

    message += 'There was an error getting the profile for ';
    // Display the users in the order they were called initially and convert array to string
    message += unknownUsers.reverse().join(', ');
    // If statusCode is 200 (because at least one username was correctly returned)
    //     we make an assumption that if a username isn't found it's because it
    //     doesn't exist, i.e. Not Found error code message
    //     Similar to if no results were found and using http.STATUS_CODES[response.statusCode]
    message += ' (Not Found). Please check the spelling or try again later';

    //StatusCode Error
    printError({ message: message });
  // Not at end of users array so carry on
  } else {
    return;
  }
}


Step 2: Handling the subject

Declare variables to handle unknown subject error

  • In profile.js after var counter = 0; and before the function declarations insert
// Trigger for exit
var time2go = false;
  • Immediately following the line, response.on('end',function() {, but above counter ++;, insert
// Trigger for valid subject
var isSubj = false;

Refactor printMessage()

  • Our main goal here is to set time2go = true when an invalid subject is found so we can print the error message only once then exit the app
  • A secondary goal is to conform the usage of the message variable to sense of standards and readability, i.e.similar to how we utilized the variable of the same name in printUnknownUsersError()
  • Another secondary goal is to return the error message, which will be handled by the catch block (explained below), instead of printing to the console from within the function
  • Replace the current body of the printMessage() function with
 var message = "";
  // The subject given is invalid
  if(points === undefined) {
    // Print invalid subject message only once
    time2go = true;
    message += "\nThe subject:("; // Add a new line for readability
    // Attach the subject that was provided
    message += subj;
    message += ") does not exist. ";
    message += "Please choose a valid subject. Subjects are case senitive.";
    message += "\nHere is a subject list: ";
    // Attach the list of subjects and format so that a
    //     space follows the comma between valid subjects
    //     for better readability
    message += subjs.join(', ');
    // Send message to be output by error handler in catch block
    return {message: message};
  } else {
    // Print user's profile info
    message = username + " has " + badgeCount + " total badges and "+ points+ " points in "+subj;
    console.log(message);
  }

Refactor parsing portion of get() (again)

+Within response.on('end',function() { and after var isSubj = undefined;, but before counter++; insert

// Invalid subject so quit
      if (time2go) {
        return false;
      }
  • Within the try block, immediately after subjs = Object.keys(profile.points).splice(1);, but before the closing bracket of the try block, replace the code with
// Check existing subjects to verify if subj is valid
 for (var i = 0; i <= subjs.length; i++) {
   // Valid subject
   if (subjs[i] === subj) {
     isSubj = true;
   }
  }

  // Invalid subject
  if (!isSubj) {
    // Print the invalid subject message
    //     - Exit try block and send to catch block
    throw printMessage("", "", undefined, subj);
  } else {
    //Print the data out
    printMessage(username,profile.badges.length,profile.points[subj],subj);
    // Print error message of unknown/ not found users if there are any
    printUnknownUsersError(counter, numUsers);
  }


Files

app.js:

var profile = require("./profile.js");

// Create String of potential subject
var subj = process.argv[2];
// Create Array of potential users
var users = process.argv.slice(3);

// Loop through each user, get their info and output
users.forEach(function(currentValue) {
  profile.get(currentValue,subj, users.length);
});

profile.js:

/* Retrieve a user's profile info
 * and
 * Print out message(s)
 */

var http = require("http");

// A user's returned profile
var profile = {};
// Array for existing subjects
var subjs = [];
// Array for users not returned
var unknownUsers = [];
// Counter for looping through provided users array
var counter = 0;
// Trigger for exit
var time2go = false;


function printMessage(username,badgeCount,points,subj) {
  var message = "";
  // The subject given is invalid
  if(points === undefined) {
    // Print invalid subject message only once
    time2go = true;
    message += "\nThe subject:("; // Add a new line for readability
    // Attach the subject that was provided
    message += subj;
    message += ") does not exist. ";
    message += "Please choose a valid subject. Subjects are case senitive.";
    message += "\nHere is a subject list: ";
    // Attach the list of subjects and format so that a
    //     space follows the comma between valid subjects
    //     for better readability
    message += subjs.join(', ');
    // Send message to be output by error handler in catch block
    return {message: message};
  } else {
    // Print user's profile info
    message = username + " has " + badgeCount + " total badges and "+ points+ " points in "+subj;
    console.log(message);
  }
}

function printUnknownUsersError(counter, numUsers) {
  // At the last item of users and at least 1 unknown user esixts
  if (counter === numUsers && unknownUsers.length > 0) {
    var message =  "\n"; // newline for spacing before this message

    message += 'There was an error getting the profile for ';
    // Display the users that were not found and convert array to string
    message += unknownUsers.join(', ');
    // If statusCode is 200 (because at least one username was correctly returned)
    //     we make an assumption that if a username isn't found it's because it
    //     doesn't exist, i.e. Not Found error code message
    //     Similar to if no results were found and using http.STATUS_CODES[response.statusCode]
    message += ' (Not Found). Please check the spelling or try again later';

    //StatusCode Error
    printError({ message: message });
  } else {
    // Not at end of users array so carry on
    return;
  }
}

//Print out Error messages
function printError(error) {
   console.error(error.message);
}

function get(username,subj, numUsers) {
  //connect to API URL(https://teamtreehouse.com/username.json)
  var req = http.get("http://teamtreehouse.com/" + username + ".json",function(response) {
    var body="";

    //Read the data from the response
    response.on('data', function (chunk) {
      body += chunk;
    });

    //Parse the data
    response.on('end',function() {
      // Trigger for valid subject
      var isSubj = false;

      // Invalid subject so quit
      if (time2go) {
        return false;
      }

      // Increase the counter as we iterate through the users array
      counter ++;

      if (response.statusCode === 200) {
        try {
          // Store the returned profile
          profile = JSON.parse(body);
          // Store a list of current subjects
          subjs = Object.keys(profile.points).splice(1);

            // Check existing subjects to verify if subj is valid
            for (var i = 0; i <= subjs.length; i++) {
              // Valid subject
              if (subjs[i] === subj) {
                isSubj = true;
              }
            }

            // Invalid subject
            if (!isSubj) {
              // Print the invalid subject message
              //     - Exit try block and send to catch block
              throw printMessage("", "", undefined, subj);
            } else {
              //Print the data out
              printMessage(username,profile.badges.length,profile.points[subj],subj);
              // Print error message of unknown/ not found users if there are any
              printUnknownUsersError(counter, numUsers);
            }

        } catch(error) {
          //Parsing Error
          printError(error);
        }

      } else {
          // Add falsey username to array
          unknownUsers.push(username);
          // Print error message of unknown/ not found users if there are any
          // We need to call this here in case all users were not found
          printUnknownUsersError(counter, numUsers);

      }
    });

    //Conncetion Error
    req.on("error",printError);
  });
}

// Expose method
module.exports.get = get;

Changelog:

Jan. 8, 2015: Added step 2 for handling of the bad subject error message to be output only once

Hossam Khalifa
Hossam Khalifa
17,200 Points

did you try this code?? because it dosen't work for me

Sean T. Unwin
Sean T. Unwin
28,690 Points

Yes, I tried the code. I just tried again now by creating new files copied from the 'Files' section above. It works for me.

What type of error are you seeing?


note: The following does not affect my original post from functioning in any way. It is an enhancement only.

In the test I just ran I did find that the unknownUsersError would print even if there weren't any found so I added a check to only print that error message if unknown users were found.

Within the printUnknownUsersError function, at approx. line 25 in profile.js, I changed the if statement from:

if (counter === numUsers) {

to:

 if (counter === numUsers && unknownUsers.length > 0) {
Hossam Khalifa
Hossam Khalifa
17,200 Points

I copied the above code for profile.js and for app.js and I get this error message The subject:(subjject) does not exist.Please choose a valid subject.Subjects are case senitive.Here is a subject list :HTML,CSS,Design,JavaScript,Ruby,PHP,WordPress,iOS,Android,Development Tools,Busin ess,Python,Java printed three times

Sean T. Unwin
Sean T. Unwin
28,690 Points

What are you typing into the console to run app.js including the parameters?

Hossam Khalifa
Hossam Khalifa
17,200 Points

node app.js iS hossamkhalifa mostafamabrouk chalkers

iS as a wrong subject

Sean T. Unwin
Sean T. Unwin
28,690 Points

Ah, I see.

So it is working the way I intended to code it as it will only print out one error message if one or more students are not found.

I didn't test for a bad subject with multiple users. I was concentrating on the users array passed in as I thought that was what you wanted.

Let me see what I can come up with when I have the time.

Sean T. Unwin
Sean T. Unwin
28,690 Points

Hossam Khalifa, I had some time to work on this today and I have updated my original post with a 'step 2' for dealing with an unknown subject's error message so that it will only be output one time.

Please, read through my answer above again to see my process of printing only one error message for an unknown subject. Futhermore, if the subject does exist, but a user does not then output an error message for an unknown user or users.

I hope I have fulfilled your intention. It sure was a fun experiment. If you have any further questions, feel free to ask. :-)

Hossam Khalifa
Hossam Khalifa
17,200 Points

Thank you very much for your help,it was really fun for me also. :D. But my only question is how or wich portion of the code in the get function exited the loop in the app.js.I don't understand how to exit it using the get() function.

Sean T. Unwin
Sean T. Unwin
28,690 Points

The exiting of the app on an unknown subject error is ultimately done when the global variable time2go is true. Near the top of the response.on('end') function there is an if statement that checks for this.

If time2go is true then the statement return false exits the response.on('end') function and since this is effectively the end of the get() function (in other words that part is the last procedure of the function) there is nothing left for the app to do so it quits.