Chapter 4. Interactive Node with REPL and More on the Console
While you’re exploring the use of Node and figuring out the code for your custom module or Node application, you don’t have to type JavaScript into a file and run it with Node to test your code. Node also comes with an interactive component known as REPL, or read-eval-print loop.
REPL (pronounced “repple”) supports simplified line editing and a small set of basic commands. Whatever you type into REPL is, for the most part, processed no differently than if you had typed the JavaScript into a file and run the file using Node. You can actually use REPL to code your entire application—literally testing the application on the fly.
In this chapter, I’ll cover how to use REPL, as well as some interesting quirks of REPL and how to work with them. These workarounds include replacing the underlying mechanism that persists commands, as well as using some command-line editing. And if the built-in REPL doesn’t provide exactly what you need for an interactive environment, there’s also an API to create your own custom REPL.
REPL is an essential Node development tool, and so is the console. We’re using the console in most applications in the book, but there’s more to this helpful object than just logging messages.
REPL: First Looks and Undefined Expressions
To begin REPL, simply type node
without providing any Node application file, like so:
$ node
REPL then provides a command-line prompt—an angle bracket (>)—by default. Anything you type from this point on is processed by the underlying V8 JavaScript engine.
REPL is very simple to use. Just start typing in your JavaScript, like you’d add it to a file:
> a = 2; 2
The tool prints out the result of whatever expression you just typed. In this session excerpt, the value of the expression is 2
. In the following, the expression result is an array with three elements:
> b = ['a','b','c']; [ 'a', 'b', 'c' ]
To access the last expression, use the underscore/underline special variable (_). In the following, a
is set to 2
, and the resulting expression is incremented by 1, and then 1 again:
> a = 2; 2 > ++_; 3 > ++_; 4
You can even access properties or call methods on the underscored expression:
> ['apple','orange','lime'] [ 'apple', 'orange', 'lime' ] > _.length 3 > 3 + 4 7 > _.toString(); '7'
You can use the var
keyword with REPL in order to access an expression or value at a later time, but you might get an unexpected result. For instance, consider the following line in REPL:
var a = 2;
It doesn’t return the value 2
; it returns a value of undefined
. The reason is that the result of the expression is undefined since variable assignment doesn’t return a result when evaluated.
Consider the following instead, which is what’s happening, more or less, under the hood in REPL:
console.log(eval('a = 2')); console.log(eval('var a = 2'));
Typing the preceding line into a file and running that file using Node returns:
2 undefined
There is no result from the second call to eval
; hence, the value returned is undefined
. REPL is a read-eval-print loop, with emphasis on the eval.
You can use the variable in REPL, just as you would in a Node application:
> var a = 2; undefined > a++; 2 > a++; 3
The latter two command lines do have results, which are printed out by REPL.
Note
I’ll demonstrate how to create your own custom REPL—one that doesn’t output undefined
—in “Custom REPL”.
To end the REPL session, either press Ctrl-C twice, or Ctrl-D once. We’ll cover other ways to end the session later, in “REPL Commands”.
Benefits of REPL: Getting a Closer Understanding of JavaScript Under the Hood
Here’s a typical demonstration of REPL:
> 3 > 2 > 1; false
This code snippet is a good example of how REPL can be useful. At first glance, we might expect the expression just typed to evaluate to true
, since 3 is greater than 2, which is greater than 1. However, in JavaScript, relational operators are evaluated left to right, and each expression’s result is returned for the next evaluation.
A better way of looking at what’s happening with the preceding code snippet is this REPL session:
> 3 > 2 > 1; false > 3 > 2; true > true > 1; false
Now the result makes more sense. What’s happening is that the expression 3 > 2 is evaluated, returning true
. But then the value of true
is compared to the numeric 1. JavaScript provides automatic data type conversion, after which true
and 1 are equivalent values. Hence, true
is not greater than 1, and the result is false
.
REPL’s helpfulness is in enabling us to discover these little interesting quirks in JavaScript and our code. Hopefully, after testing our code in REPL, we don’t have unexpected side effects in our applications (such as expecting a result of true
but getting a result of false
).
Multiline and More Complex JavaScript
You can type the same JavaScript into REPL just like you’d type it into a file, including require
statements to import modules. A session to try out the Query String (qs
) module is repeated in the following text:
$
node
>
qs
=
require
(
'
querystring
'
);
{
unescapeBuffer:
[
Function
],
unescape:
[
Function
],
escape:
[
Function
],
encode:
[
Function
],
stringify:
[
Function
],
decode:
[
Function
],
parse:
[
Function
]
}
>
val
=
qs
.
parse
(
'
file
=
main
&
file
=
secondary
&
test
=
one
'
).
file
;
[
'
main
'
,
'
secondary
'
]
Since we didn’t use the var
keyword, the expression result is printed out—in this instance, it is the interface for the querystring
object. How’s that for a bonus? Not only are you getting access to the object, but you’re also learning more about the object’s interface while you’re at it. However, if you want to forgo the potentially lengthy output of text, use the var
keyword:
>
var
qs
=
require
(
'
querystring
'
);
You’ll be able to access the querystring
object with the qs
variable with either approach.
In addition to being able to incorporate external modules, REPL gracefully handles multiline expressions, providing a textual indicator of code that’s nested following an opening curly brace ({):
> var test = function (x, y) { ... var val = x * y; ... return val; ... }; undefined > test(3,4); 12
REPL displays repeating dots to indicate that everything that’s being typed follows an open curly brace and hence the command isn’t finished yet. It does the same for an open parenthesis, too:
> test(4, ... 5); 20
Increasing levels of nesting generates more dots; this is necessary in an interactive environment, where you might lose track of where you are as you type:
> var test = function (x, y) { ... var test2 = function (x, y) { ..... return x * y; ..... } ... return test2(x,y); ... } undefined > test(3,4); 12 >
You can type in, or copy and paste in, an entire Node application and run it from REPL. I trimmed the actual display values for the Server, shown in bold, both because they’re so long, and because they’ll most likely change by the time you read this:
>
var
http
=
require
(
'
http
'
)
;
undefined
>
http
.
createServer
(
function
(
req
,
res
)
{
.
.
.
.
.
.
// content header
.
.
.
res
.
writeHead
(
200
,
{
'
Content
-
Type
'
:
'
text
/
plain
'
}
)
;
.
.
.
.
.
.
res
.
end
(
"Hello person\n"
)
;
.
.
.
}
)
.
listen
(
8124
)
;
{
domain:
{
domain:
null
,
_events:
{
error:
[
Function
]
}
,
_maxListeners:
undefined
,
members:
[
]
}
,
_
.
.
.
httpAllowHalfOpen:
false
,
timeout:
120000
,
_pendingResponseData:
0
,
_connectionKey:
'
6
:
null
:
8124
'
}
>
console
.
log
(
'
Server
running
at
http:
//127.0.0.1:8124/');
Server
running
at
http:
//127.0.0.1:8124/
Undefined
You can access this application from a browser no differently than if you had typed the text into a file and run it using Node.
The “downside” of not assigning an expression to the variable is that you are going to get what could be a long object display in the midst of your application. However, one of my favorite uses of REPL is to get a quick look at objects. For instance, the Node core object global
is sparsely documented at the Node.js website. To get a better look, I opened up a REPL session and passed the object to the console.log
method like so:
>
console
.
log
(
global
)
I could have done the following, which has a similar result, with the addition of the gl variable (trimmed, for space):
>
gl
=
global
;
...
_:
[
Circular
],
gl:
[
Circular
]
}
Just printing out global
also provides the same information:
> global
I’m not replicating what was displayed in REPL. I’ll leave that for you to try on your own installation, since the interface for global
is so large. The important point to take away from this exercise is that we can, at any time, quickly and easily get a look at an object’s interface. It’s a handy way of remembering what a method is called or what properties are available.
Note
There’s more on global
in Chapter 2.
You can use the up and down arrow keys to traverse through the commands you’ve typed into REPL. This can be a handy way of reviewing what you’ve done, as well as a way of editing what you’ve typed, though in a somewhat limited capacity.
Consider the following session in REPL:
>
var
myFruit
=
function
(
fruitArray
,
pickOne
)
{
...
return
fruitArray
[
pickOne
-
1
];
...
}
undefined
>
fruit
=
[
'
apples
','
oranges
','
limes
','
cherries
'
];
[
'
apples
'
,
'
oranges
'
,
'
limes
'
,
'
cherries
'
]
>
myFruit
(
fruit
,
2
);
'
oranges
'
>
myFruit
(
fruit
,
0
);
undefined
>
var
myFruit
=
function
(
fruitArray
,
pickOne
)
{
...
if
(
pickOne
<=
0
)
return
'
invalid
number
'
;
...
return
fruitArray
[
pickOne
-
1
];
...
};
undefined
>
myFruit
(
fruit
,
0
);
'
invalid
number
'
>
myFruit
(
fruit
,
1
);
'
apples
'
Though it’s not demonstrated in this printout, when I modified the function to check the input value, I actually arrowed up through the content to the beginning function declaration, and then hit Enter to restart the function. I added the new line, and then again used the arrow keys to repeat previously typed entries until the function was finished. I also used the up arrow key to repeat the function call that resulted in an undefined
result.
It seems like a lot of work just to avoid retyping something so simple, but consider working with regular expressions, such as the following:
> var ssRe = /^\d{3}-\d{2}-\d{4}$/; undefined > ssRe.test('555-55-5555'); true > var decRe = /^\s*(\+|-)?((\d+(\.\d+)?)|(\.\d+))\s*$/; undefined > decRe.test(56.5); true
I’m absolutely useless when it comes to regular expressions, and have to tweak them several times before they’re just right. Using REPL to test regular expressions is very attractive. However, retyping long regular expressions would be a monstrous amount of work.
Luckily, all we have to do with REPL is arrow up to find the line where the regular expression was created, tweak it, hit Enter, and continue with the next test.
In addition to the arrow keys, you can also use the Tab key to autocomplete a command if there’s no confusion as to what you’re completing. As an example, type va
at the command line and then press Tab; REPL lists out both var
and valueOf
, both of which can complete the typing. However, typing querys
and hitting Tab returns the only available option, querystring
. You can also use the Tab key to autocomplete any global or local variable. Table 4-1 offers a quick summary of keyboard commands that work with REPL.
Keyboard entry | What it does |
---|---|
Ctrl-C |
Terminates current command. Pressing Ctrl-C twice forces an exit. |
Ctrl-D |
Exits REPL. |
Tab |
Autocompletes global or local variable. |
Up arrow |
Traverses up through command history. |
Down arrow |
Traverses down through command history. |
Underscore (_) |
References result of last expression. |
If you’re concerned about spending a lot of time coding in REPL with nothing to show for it when you’re done, no worries: you can save the results of the current context with the .save
command. It and the other REPL commands are covered in the next section.
REPL Commands
REPL has a simple interface with a small set of useful commands. In the preceding section, I mentioned .save
. The .save
command saves your inputs in the current object context into a file. Unless you specifically created a new object context or used the .clear
command, the context should comprise all of the input in the current REPL session:
>
.
save
./
dir
/
session
/
save
.
js
Only your inputs are saved, as if you had typed them directly into a file using a text editor.
Here is the complete list of REPL commands and their purposes:
.break
-
If you get lost during a multiline entry, typing
.break
will start you over again. You’ll lose the multiline content, though. .clear
-
Resets the context object and clears any multiline expression. This command basically starts you over again.
.exit
-
Exits REPL.
.help
-
Displays all available REPL commands.
.save
-
Saves the current REPL session to a file.
.load
-
Loads a file into the current session (
.load /path/to/file.js
).
If you’re working on an application using REPL as an editor, here’s a hint: save your work often using .save
. Though current commands are persisted to history, trying to re-create your code from history would be a painful exercise.
Speaking of persistence and history, now let’s go over how to customize both with REPL.
REPL and rlwrap
The Node.js website documentation for REPL mentions setting up an environmental variable so you can use REPL with rlwrap
. What is rlwrap
, and why would you use it with REPL?
The rlwrap
utility is a wrapper that adds GNU readline
library functionality to command lines that allow increased flexibility with keyboard input. It intercepts keyboard input and provides additional functionality, such as enhanced line editing, as well as a persistent history of commands.
You’ll need to install rlwrap
and readline
to use this facility with REPL, and most flavors of Unix provide an easy package installation. For instance, in my own Ubuntu system, installing rlwrap
was this simple:
apt-get install rlwrap
Mac users should use the appropriate installer for these applications. Windows users have to use a Unix environmental emulator, such as Cygwin.
Here’s a quick and visual demonstration of using REPL with rlwrap
to change the REPL prompt to purple:
NODE_NO_READLINE
=
1
rlwrap
-
ppurple
node
If I always want my REPL prompt to be purple, I can add an alias to my bashrc file:
alias
node
=
"NODE_NO_READLINE=1 rlwrap -ppurple node"
To change both the prompt and the color, I’d use the following:
NODE_NO_READLINE
=
1
rlwrap
-
ppurple
-
S
"::> "
node
Now my prompt would be:
::>
but in purple.
The especially useful component of rlwrap
is its ability to persist history across REPL sessions. By default, we have access to command-line history only within a REPL session. By using rlwrap
, the next time we access REPL, we’ll have access not only to a history of commands within the current session, but also to a history of commands in past sessions (and other command-line entries). In the following session output, the commands shown were not typed in, but were instead pulled from history with the up arrow key, after I had exited REPL and then re-entered it:
::> e = ['a','b']; [ 'a', 'b' ] ::> 3 > 2 > 1; false
As helpful as rlwrap
is, we still end up with undefined
every time we type in an expression that doesn’t return a value. However, we can adjust this, and other functionality, just by creating our own custom REPL, discussed next.
Custom REPL
Node provides an API that we can use to create a custom REPL. To do so, first we need to include the REPL module (repl
):
var
repl
=
require
(
"repl"
);
To create a new REPL, we call the start
method on the repl
object. The syntax for this method is:
repl
.
start
(
options
);
The options
object takes several values; the ones I want to focus on are:
prompt
-
Default is
>
. input
-
Readable stream; default is
process.stdin
. output
-
writable stream; default is
process.stdout
. eval
-
Default is an
async
wrapper foreval
. useGlobal
-
Default is
false
to start a new context rather than use the global object. useColors
-
Whether
writer
function should use colors. Defaults to REPL’sterminal
value. ignoreUndefined
-
Default is
false
: don’t ignore theundefined
responses. terminal
-
Set to
true
if stream should be treated like a tty (terminal), including support for ANSI/VT100 escape codes. writer
-
Function to evaluate each command, and return formatting. Defaults to
util.inspect
. replMode
-
Whether REPL runs in strict mode, default, or hybrid.
Note
Starting in Node 5.8.0, repl.start()
no longer requires an options
object.
I find the undefined
expression result in REPL to be unedifying, so I created my own REPL. I also redefined the prompt and set the mode to strict, which means each line executed is done so under “use strict.”
var
repl
=
require
(
'
repl
'
);
repl
.
start
(
{
prompt:
'
my
repl
>
'
,
replMode:
repl
.
REPL_MODE_STRICT
,
ignoreUndefined:
true
,
});
I ran the file, repl.js, using Node:
node
repl
I can use the custom REPL just like I use the built-in version, except now I have a different prompt and no longer get the annoying undefined
after the first variable assignment. I do still get the other responses that aren’t undefined
:
my repl> let ct = 0; my repl> ct++; 0 my repl> console.log(ct); 1 my repl> ++ct; 2 my repl> console.log(ct); 2
In my code, I wanted the defaults used for all but the listed properties. Not listing any other property in the options
object causes each to use the default.
You can replace the eval
function with your custom REPL. The only requirement is that it has a specific format:
function
eval
(
cmd
,
callback
)
{
callback
(
null
,
result
);
}
The input
and output
options are interesting. You can run multiple versions of REPL, taking input from both the standard input (the default), as well as sockets. The documentation for REPL at the Node.js site provides an example of a REPL listening in on a TCP socket, using the following code:
var
net
=
require
(
"net"
),
repl
=
require
(
"repl"
);
connections
=
0
;
repl
.
start
({
prompt:
"node via stdin> "
,
input:
process
.
stdin
,
output:
process
.
stdout
});
net
.
createServer
(
function
(
socket
)
{
connections
+=
1
;
repl
.
start
({
prompt:
"node via Unix socket> "
,
input:
socket
,
output:
socket
}).
on
(
'
exit
'
,
function
()
{
socket
.
end
();
})
}).
listen
(
"/tmp/node-repl-sock"
);
net
.
createServer
(
function
(
socket
)
{
connections
+=
1
;
repl
.
start
({
prompt:
"node via TCP socket> "
,
input:
socket
,
output:
socket
}).
on
(
'
exit
'
,
function
()
{
socket
.
end
();
});
}).
listen
(
5001
);
When you run the application, you get the standard input prompt where the Node application is running. However, you can also access REPL via TCP. I used PuTTY as a Telnet client to access this TCP-enabled version of REPL. It does work…to a point. I had to issue a .clear
first, the formatting is off, and when I tried to use the underscore to reference the last expression, Node didn’t know what I was talking about. I also tried with the Windows Telnet client, and the response was even worse. However, using my Linux Telnet client worked without a hitch.
The problem here, as you might expect, is Telnet client settings. However, I didn’t pursue it further, because running REPL from an exposed Telnet socket is not something I plan to implement, and not something I would recommend, either—at least, not without heavy security. It’s like using eval()
in your client-side code, and not scrubbing the text your users send you to run—but worse.
You could keep a running REPL and communicate via a Unix socket with something like the GNU Netcat utility:
nc -U /tmp/node-repl-sock
You can type in commands no differently than typing them in using stdin
. Be aware, though, if you’re using either a TCP or Unix socket, that any console.log
commands are printed out to the server console, not to the client:
console.log(someVariable); // actually printed out to server
An application option that I consider to be more useful is to create a REPL application that preloads modules. In the application in Example 4-1, after the REPL is started, the third-party modules of Request (powerful HTTP client), Underscore (utility library), and Q (promise management) modules are loaded and assigned to context properties.
Example 4-1. Creating a custom REPL that preloads modules
var
repl
=
require
(
'
repl
'
);
var
context
=
repl
.
start
({
prompt:
'
>>
'
,
ignoreUndefined:
true
,
replMode:
repl
.
REPL_MODE_STRICT
}).
context
;
// preload in modules
context
.
request
=
require
(
'
request
'
);
context
.
underscore
=
require
(
'
underscore
'
);
context
.
q
=
require
(
'q'
);
Running the application with Node brings up the REPL prompt, where we can then access the modules:
>>
request
(
'
http:
//blipdebit.com/phoenix5a.png')
.
pipe
(
fs
.
createWriteStream
(
'
bird
.
png
'
))
The core Node modules don’t need to be specifically included; just access them by their module name directly.
If you want to run the REPL application like an executable in Linux, add the following line as the first line in the application:
#!/usr/local/bin/node
Modify the file to be an executable and run it:
$ chmod u+x replcontext.js $ ./replcontext.js >>
Stuff Happens—Save Often
Node’s REPL is a handy interactive tool that can make our development tasks a little easier. REPL allows us not only to try out JavaScript before including it in our files, but also to actually create our applications interactively and then save the results when we’re finished.
Another useful REPL feature is that it enables us to create a custom REPL so that we can eliminate the unhelpful undefined
responses, preload modules, change the prompt or the eval
routine we use, and more.
I also strongly recommend that you look into using REPL with rlwrap
in order to persist commands across sessions. This could end up being a major time saver. Plus, who among us doesn’t like additional editing capability?
As you explore REPL further, there’s one very important thing to keep in mind from this chapter: stuff happens. Save often.
If you’ll be spending a lot of time developing in REPL, even with the use of rlwrap
to persist history, you’re going to want to frequently save your work. Working in REPL is no different than working in other editing environments, so I’ll repeat: stuff happens—save often.
The Necessity of the Console
There are few examples in this book that don’t make use of the console. The console is a way of printing out values, checking operations, verifying the asynchronous nature of an application, and providing some kind of feedback.
For the most part, we’re using console.log()
and just printing out messages. But there’s more to the console than a server version of the alert box in the browser.
Console Message Types, Console Class, and Blocking
In most of the examples in the book, we’re using console.log()
because we’re only interested in feedback while we’re experimenting with Node. This function outputs the message to stdout
, typically the terminal. When you start putting together Node applications for a production environment, though, you’re going to want to make use of other console messaging functions.
The console.info()
function is equivalent to console.log()
. Both write to stdout
; both output a newline character as part of the message. The console.error()
function differs in that it outputs the message (again, with a newline character) to stderr
, rather than stdout
:
console.error("An error occurred...");
The console.warn()
function does the same.
Both types of messaging appear in the terminal, so you might wonder what the difference is. In actuality, there really is no difference. To understand that, we need to look more closely at the console
object.
Using a Logging Module
You’re not limited to the built-in console
object for logging. There are more sophisticated tools available, such as the Bunyan and Winston modules.
First of all, the console
object is a global object instantiated from the Console
class. We can, if we wish, actually create our own version of the console
using this same class. And we can do so in two different ways.
To create a new instance of Console
, we either need to import the Console
class or access it via the global console
object. Both of the following result in a new console-like object:
// using require
var
Console
=
require
(
'
console
'
).
Console
;
var
cons
=
new
Console
(
process
.
stdout
,
process
.
stderr
);
cons
.
log
(
'
testing
'
);
// using existing console object
var
cons2
=
new
console
.
Console
(
process
.
stdout
,
process
.
stderr
);
cons2
.
error
(
'
test
'
);
Notice how in both instances, the process.stdout
and process.stderr
properties are passed as writable stream instances for writing log messages and error messages, respectively. The console
global object is created in just this manner.
I covered process.stdout
and process.stderror
in Chapter 2. What we know about both is that they map to the stdout
and stderr
file descriptors in the environment, and that they’re different from most streams in Node in that they typically block—they’re synchronous. The only time they’re not synchronous is when the streams are directed at a pipe. So, for the most part, the console
object blocks for both console.log()
and console.error()
. However, this isn’t an issue unless you’re directing a large amount of data at the stream.
So why use console.error()
when an error occurs? Because in environments where the two streams are different, you want to ensure the proper behavior. If you’re in an environment where log messages don’t block but an error does, you want to ensure a Node error does block. In addition, when you run a Node application, you can direct the output for console.log()
and console.error()
to different files using command-line redirection. The following directs the console.log()
messages to a logfile, and the errors to an error file:
node app.js 1> app.log 2> error.log
The following Node application:
// log messages
console
.
log
(
'
this
is
informative
'
);
console
.
info
(
'
this
is
more
information
'
);
// error messages
console
.
error
(
'
this
is
an
error
'
);
console
.
warn
(
'
but
this
is
only
a
warning
'
);
directs the first two lines to app.log, and the second two to error.log.
To return to the Console
class, you can duplicate the functionality of the global console
object by using the Console
class and passing in process.stdout
and process.stderr
. You can also create a new console-like object that directs the output to different streams, such as log and error files. The Console
documentation provided by the Node Foundation provides just such an example:
var output = fs.createWriteStream('./stdout.log'); var errorOutput = fs.createWriteStream('./stderr.log'); // custom simple logger var logger = new Console(output, errorOutput); // use it like console var count = 5; logger.log('count: %d', count); // in stdout.log: count 5
The advantage to using this type of object is that you can use the global console
for general feedback, reserving the newly created object for more formal reporting.
Formatting the Message, with Help from util.format() and util.inspect()
All four console
functions, log()
, warn()
, error()
, and info()
, can take any data type, including an object. Non-object values that aren’t a string are coerced to a string. If the data type is an object, be aware that Node only prints out two levels of nesting. If you want more, you should use JSON.stringify()
on the object, which then prints out a more readable indented tree:
var
test
=
{
a
:
{
b
:
{
c
:
{
d
:
'test'
}
}
}
}
//
only
two
levels
of
nesting
are
printed
out
console.log(test);
//
three
levels
of
nesting
are
printed
var
str
=
JSON.stringify(test,
null
,
3
);
console.log(str);
The output of the application is:
{ a: { b: { c: [Object] } } } { "a": { "b": { "c": { "d": "test" } } } }
If you use a string, you can use printf-like formatting with the string for all four functions:
var
val
=
10.5
;
var
str
=
'
a
string
'
;
console
.
log
(
'
The
value
is
%
d
and
the
string
is
%
s
'
,
val
,
str
);
This approach is advantageous if you’re working with data passed as function arguments or collected from a web request. The type of formatting allowed is based on the formatting supported for the util.format()
utilities module, which you could also use directly to create the string:
var
util
=
require
(
'
util
'
);
var
val
=
10.5
,
str
=
'
a
string
'
;
var
msg
=
util
.
format
(
'
The
value
is
%
d
and
the
string
is
%
s
'
,
val
,
str
);
console
.
log
(
msg
);
Though if you’re only using the one function, it’s simpler to just use the formatting in console.log()
. Allowable format values are:
%s
- string
%d
- number (both integer and float)
%j
- JSON. Replaced with [
'circular'
] if the argument contains circular references %%
- to use a literal percentage sign (%)
Extra arguments are converted to strings and concatenated to the output. If there are too few arguments, the placeholder itself is printed out:
var val = 3; // results in 'val is 3 and str is %s' console.log('val is %d and str is %s', val);
The four functions covered are not the only ones used to provide feedback. There’s also console.dir()
.
The console.dir()
function differs from the other feedback functions in that whatever object is passed to it is passed, in turn, to util.inspect()
. This Utilities module function provides more finite control over how an object is displayed via a secondary options
object. Like util.format()
, it can also be used directly. An example is:
var
test
=
{
a
:
{
b
:
{
c
:
{
d
:
'
test
'
}
}
}
}
var
str
=
require
(
'
util
'
).
inspect
(
test
,
{
showHidden:
true
,
depth:
4
});
console
.
log
(
str
);
The object is inspected and the result is returned as a string based on what is passed in the options
object. Options are:
showHidden
: to display non-enumerable or symbolic properties (default isfalse
)depth
: how many times to recurse to inspect object (default is2
)colors
: iftrue
, output is styled with ANSI color codes (default isfalse
)customInspect
: iffalse
, then custom inspect functions defined on the objects being inspected won’t be called (default istrue
)
The color mapping can be defined globally using the util.inspect.styles
object. You can modify the global colors, too. Use console.log()
to print out the object properties:
var
util
=
require
(
'
util
'
);
console
.
log
(
util
.
inspect
.
styles
);
console
.
log
(
util
.
inspect
.
colors
);
The application in Example 4-2 modifies the object being printed to add a date, a number, and a boolean. In addition, the color value for the boolean is changed from yellow to blue, to differentiate it from the number (by default, they’re both yellow). The object is printed out using various methods: after processing with util.inspect()
, using console.dir()
with same options, using the basic console.log()
function, and using the JSON.stringify()
function on the object.
Example 4-2. Different formatting options for printing out an object
var
util
=
require
(
'
util
'
);
var
today
=
new
Date
();
var
test
=
{
a
:
{
b
:
{
c
:
{
d
:
'
test
'
},
c2
:
3.50
},
b2
:
true
},
a2:
today
}
util
.
inspect
.
styles
.
boolean
=
'
blue
'
;
// output with util.inspect direct formatting
var
str
=
util
.
inspect
(
test
,
{
depth:
4
,
colors:
true
});
console
.
log
(
str
);
// output using console.dir and options
console
.
dir
(
test
,
{
depth:
4
,
colors:
true
});
// output using basic console.log
console
.
log
(
test
);
// and JSON stringify
console
.
log
(
JSON
.
stringify
(
test
,
null
,
4
));
The result is shown in Figure 4-1 so you can see the color effects. I use a white background for my terminal window, with black text.
Providing Richer Feedback with console and a Timer
Returning to the console
object, one additional approach that can provide a more in-depth picture of what’s happening with an application is to add a timer and output a begin and end time.
The two console functions we’re using for this functionality are console.time()
and console.timeEnd()
, passing a timer name to both.
In the following code snippet, the code is using a longer-lasting loop so that enough time passes for the timer functions to register a time difference.
console.time('the-loop'); for (var i = 0; i < 10000; i++) { ; } console.timeEnd('the-loop');
Even with a largish loop, the time involved will barely register. How long depends on the load in the machine, as well as the process. But the timer functionality isn’t limited to synchronous events. Being able to use a specific name with the timer means we can use the functionality with asynchronous events.
Here, I modify the Hello World application from Chapter 1, adding in a start to the timer at the beginning of the application, ending it for each web request, and then re-starting it. It times the gaps in time between each request. Sure, we could use a Date()
function to do a more representative timer, but what’s the fun of that?
var http = require('http'); console.time('hello-timer'); http.createServer(function (request, response) { response.writeHead(200, {'Content-Type': 'text/plain'}); response.end('Hello World\n'); console.timeEnd('hello-timer'); console.time('hello-timer'); }).listen(8124); console.log('Server running at http://127.0.0.1:8124/');
In all seriousness, what the code demonstrates is that you can incorporate the timer into asynchronous operations, thanks to the ability to individually name the timers.
Get Learning Node, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.