Input/Output

Computers and software don't live in isolation. They are tools that solve real-world problems, which means that they must interact with the real world. A computer needs to take input from some source, perform some actions on that input, and then provide output.

Computer programs can use a multitude of input sources, such as mice, keyboards, disks, the network, and environmental sensors. Similarly, a program can write output to multiple places, such as the screen, a log, or a network.

A computer can obtain input from another computer's output. For example, when you enter your login credentials for a website, your computer encrypts those credentials and sends them to the website's authentication server, which then inputs the data and attempts to verify your identity. In this fashion, two computers can work together to perform different parts of a common task.

In this chapter, we'll discuss the most basic method that a program can use to interact with the outside world: reading keyboard input from the command line and displaying output.

Command Line Output

You've already seen one method for writing data: console.log. This built-in function takes any JavaScript value, regardless of type, and logs it to the console. Let's see an example. Create a file named greetings.js with the following content:

let name = 'Jane';
console.log(`Good morning, ${name}!`);

In this simple example we initialize a name variable with the string 'Jane', then use it in a template literal string to build and display a greeting message. Run the program with node to see it in action:

> node greetings.js
Good morning, Jane!

There are other ways to send messages to the console in node, but console.log works in both node and most browsers. You can run the above code in a browser and see the same results in the browser's console. The Preparations chapter shows you how to run JavaScript in a browser and how to open Chrome's developer console.

Command Line Input

In the previous example, we assigned a value to name and displayed a fixed greeting message that uses that name. That's probably not the most exciting thing you've seen today. We hard-coded the name into a variable, which means that the program will always use the same name. If we want to greet a different person, we have to modify the program and run it again. You probably wouldn't bother using a program that worked like that.

Can we ask the user to provide her name and greet her with a message that uses the name she entered? Yes, we can. Node.js has an API called readline that lets JavaScript programs read input from the command line. However, the API isn't straightforward or simple: it requires an understanding of asynchronous programming and higher-order functions. We don't explore these concepts in this book. For now, we can use a simplified version of the readline library called readline-sync.

To install readline-sync, you must first create a package.json file in the current directory. Use the ls command to see if there is one already:

$ ls package.json
package.json

If you see this output, you already have a package.json file and can skip the rest of this info box. If, however, you see an error message, you need to run the npm init -y command:

$ ls package.json
ls: cannot access 'package.json': No such file or directory

$ npm init -y
Wrote to .../package.json:

{
  "name": "Downloads",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Once you have a package.json file, you can install readline-sync as follows:

$ npm install readline-sync --save
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN Downloads@1.0.0 No description
npm WARN Downloads@1.0.0 No repository field.

+ readline-sync@1.4.10
added 1 package from 1 contributor and audited 1 package in 0.423s
found 0 vulnerabilities

You should see output similar to that shown above. You can usually ignore those messages unless you see some npm ERR! messages.

The npm install command with a --save option installs the package in the node_modules subdirectory of your current directory. By placing it in this subdirectory, any Node.js programs stored in the current directory can require the package with a simple call to require, as we'll see in the following example:

Example: Greet the User By Name

Let's use readline-sync to write a program that displays a personalized greeting message for the user based on the name she provides. Create a file named personalized_greeting.js with the following code:

let rlSync = require('readline-sync');
let name = rlSync.question("What's your name?\n");
console.log(`Good Morning, ${name}!`);

Run the program from the command line with node. It displays the What's your name? question, then waits for your input. When you enter a name and hit Return, the program displays a custom greeting message:

$ node personalized_greeting.js
What's your name?
Sarah
Good Morning, Sarah!

This example needs a little explanation. Line 1 uses Node's built-in require function to import readline-sync into your program. It returns the library as an object, which we assign to the rlSync variable.

In line 2, we use rlSync to call the question method. This method displays its string argument, then waits for the user to respond. When the user types some text and presses Return, it returns that text to the program. Here, we assign that text to the variable name and use it to display a personalized greeting.

Example: Add Two Numbers Together

Let's write a program that asks for two numbers from the user, adds them, then displays the result. Put the following code in a new file called sum_numbers.js and then run it:

let rlSync = require('readline-sync');

let number1 = rlSync.question('Enter the first number\n');
let number2 = rlSync.question('Enter the second number\n');
let sum = number1 + number2;

console.log(`The numbers ${number1} and ${number2} add to ${sum}`);
$ node sum_numbers.js
Enter the first number
2
Enter the second number
3
The numbers 2 and 3 add to 23

Hmm. Something isn't right. The program reports that the result is 23, not 5 like we want. Where do you think the problem lies? If you said that question returns strings so + performs concatenation, you're right! Since number1 and number2 both hold string values instead of numbers, the + operator concatenates them instead of adding them. If you want to add two variables arithmetically, both variables must have the Number data type.

As we learned earlier, we can convert (coerce) strings to numbers with the Number function. Update lines 3 and 4 from sum_numbers.js to match this code:

let rlSync = require('readline-sync');

#highlight
let number1 = Number(rlSync.question('Enter the first number\n'));
let number2 = Number(rlSync.question('Enter the second number\n'));
#endhighlight
let sum = number1 + number2;

console.log(`The numbers ${number1} and ${number2} add to ${sum}`);

rlSync.question() still returns a string, but this time we coerce each string to a number with the Number function. With numbers in both variables, JavaScript adds them arithmetically. Verify that the revised program reports the answer as 5.

Be sure to play with these examples a bit. You can, for example, replace addition with subtraction or multiplication in the above example. Try to think of some applications of rlSync.question on your own.

In general, I/O (Input/Output) in Node is a complex topic that requires knowledge of concepts that we haven't learned yet. They're essential concepts, but we don't discuss them in this book; we cover them in the Core Curriculum. Until then, we can use readline-sync to interact with users.

Input in the Browser

Browsers provide an environment that differs radically from that of Node.js: browser users must interact with JavaScript programs in an entirely different way. Real-world browser applications let users click on buttons, enter text, select checkboxes and radio buttons, and even adjust sliders. These actions provide input to the application, which then performs work in response, such as changing the page content, sending data to a database, or displaying some additional content.

Working with a browser's input controls requires a working knowledge of the Document Object Model (DOM), which is outside the scope of this book. However, you don't need to know about the DOM to get user inputs. Most browsers implement the prompt function which lets a program ask for and obtain text-based input from the user.

Let's re-implement our personalized greeting program for the browser. First, we need to create an HTML file with the following content:

<!DOCTYPE html>
<html>
<head>
  <title>Testing Prompt</title>
</head>
<body>
  <script src="personalized_greeting_browser.js"></script>
</body>
</html>

If you're unfamiliar with HTML, don't worry. All you need to know right now is that HTML filenames typically end with .html, and the src attribute in the <script> tag on line 7 identifies the name of a JavaScript file. When your browser encounters this tag, it loads the JavaScript file and executes the code that it contains.

Since our HTML expects a personalized_greeting_browser.js file, go ahead and create one with the code shown below. Place it in the same directory as the HTML file:

let name = prompt("What's your name?");
console.log(`Good Morning, ${name}`);

Open the HTML file in Chrome. You can use Chrome's File, Open File menu option. If you're using Windows without a menu bar in Chrome, try using Control-O. You should see a dialog that looks something like this:

chrome's prompt

The prompt function works much like rlSync.question: it waits for the user to input some text and click the 'OK' button, and then returns the user's input as a string. As with the Node.js version of this program, we can assign the return value to a variable and use it later in the program. In this example, our program uses the string to build a personalized greeting message and logs it to the console.

Summary

These examples are simple, but they demonstrate how you can obtain user input, perform some work with that input, and then use the results to show some output to the user. This input/output cycle is at the heart of every computer program. After all, software that accepts no input and provides no output is useless.

Exercises

  1. Write a dynamic greeter program named greeter.js. The program should ask for your name, then output Hello, {name}! where {name} is the name you entered:

    $ node greeter.js
    What is your name? Sue
    Hello, Sue!
    

    Solution

    let readlineSync = require('readline-sync');
    let name = readlineSync.question('What is your name? ');
    console.log('Hello, ' + name + '!');
    
    let readlineSync = require('readline-sync');
    let name = readlineSync.question('What is your name? ');
    console.log(`Hello, ${name}!`);
    
    let name = prompt('What is your name? ');
    console.log(`Hello, ${name}!`);
    

    Video Walkthrough

    Please register to play this video

  2. Modify the greeter.js program to ask for the user's first and last names separately, then greet the user with their full name.

    $ node greeter.js
    What is your first name? Sue
    What is your last name? Roberts
    Hello, Sue Roberts!
    

    Solution

    let readlineSync = require('readline-sync');
    let firstName = readlineSync.question('What is your first name? ');
    let lastName = readlineSync.question('What is your last name? ');
    console.log(`Hello, ${firstName} ${lastName}!`);
    

    Video Walkthrough

    Please register to play this video

  3. Modify the age.js program you wrote in the exercises for the Variables chapter. The updated code should ask the user to enter their age instead of hard-coding the age in the program. Here's an example run:

    How old are you? 22
    You are 22 years old.
    In 10 years, you will be 32 years old.
    In 20 years, you will be 42 years old.
    In 30 years, you will be 52 years old.
    In 40 years, you will be 62 years old.
    

    Solution

    let readlineSync = require('readline-sync');
    let age = readlineSync.question('How old are you? ');
    age = parseInt(age);
    console.log(`You are ${age} years old.`);
    console.log(`In 10 years, you will be ${age + 10} years old.`);
    console.log(`In 20 years, you will be ${age + 20} years old.`);
    console.log(`In 30 years, you will be ${age + 30} years old.`);
    console.log(`In 40 years, you will be ${age + 40} years old.`);
    

    Video Walkthrough

    Please register to play this video