Skip to content
academia.sh

Lesson 03 / 20

The Process Object

The argument array, environment variables, the object counterpart of the standard streams, the two ways to set an exit code, and the exit hook.

Contents

The previous lesson examined the loop’s turns. The program running inside those turns talks to the outside world not only through files and the network but also with the environment that started it: what arguments was it called with, which environment variables did it inherit, where is its output connected, and what will it report to its caller when it ends?

All of this is gathered in a single global object. What a shell script did with $1, $?, and $HOME in the Shell Programming course, this object’s fields do here.

Arguments and Environment

process.argv carries the command line the process was started with, as an array of strings. The array’s first two elements are fixed: the executable’s path and the running main module’s path. The arguments the user typed start at the third element.

// process-info.mjs
console.log('argv[0] last part:', process.argv[0].split('/').pop());
console.log('argv[1] last part:', process.argv[1].split('/').pop());
console.log('remaining args   :', process.argv.slice(2));
console.log('platform         :', process.platform);
console.log('working dir      :', process.cwd().split('/').pop());
console.log('MEASUREMENTS_DIR :', process.env.MEASUREMENTS_DIR ?? '(undefined)');
node process-info.mjs --node edge-01 --format summary
argv[0] last part: node
argv[1] last part: process-info.mjs
remaining args   : [ '--node', 'edge-01', '--format', 'summary' ]
platform         : darwin
working dir      : lab
MEASUREMENTS_DIR : (undefined)

The full paths on the first two lines vary by machine, so the example prints only their last parts. platform gives a different value per operating system; working dir depends on which directory you called the command from. The next topic covers the right way to pin paths down.

Environment variables are read through process.env. Every value is a string; if you expect a number, converting and validating it is on you.

MEASUREMENTS_DIR=/data/measurements node process-info.mjs
argv[0] last part: node
argv[1] last part: process-info.mjs
remaining args   : []
platform         : darwin
working dir      : lab
MEASUREMENTS_DIR : /data/measurements

Writing the variable before the command defines it only for that call; the shell session is unaffected — the same environment-inheritance rule from the Shell Programming course.

The Object Counterpart of the Standard Streams

In the Shell Programming course, the three standard streams were referred to by number: 0 input, 1 output, 2 error. The runtime offers these three as objects: process.stdin, process.stdout, process.stderr. Not ordinary objects but stream objects, detailed in later lessons; for now, their write methods are enough.

The stream-separation rule holds here too: data goes to standard output, every description about it goes to standard error. The tool below reads the measurement file and prints two tab-separated columns; the counter and error messages go to the second stream.

// stream-separation.mjs
import { readFileSync } from 'node:fs';

const file = process.argv[2] ?? 'measurements.ndjson';

let text;
try {
  text = readFileSync(file, 'utf8');
} catch (error) {
  process.stderr.write(`error: could not read ${file} (${error.code})\n`);
  process.exitCode = 66;                    // no data input
  text = '';
}

let count = 0;
for (const line of text.split('\n')) {
  if (line.trim() === '') continue;
  const { node, value } = JSON.parse(line);
  process.stdout.write(`${node}\t${value}\n`);
  count += 1;
}

process.stderr.write(`${count} records processed\n`);

process.on('exit', (code) => {
  process.stderr.write(`process exiting with code ${code}\n`);
});

To see only the data, the second stream is discarded:

node stream-separation.mjs 2>/dev/null | head -3
edge-01	21.4
edge-01	48
edge-02	19.8

To see only the diagnostic messages, the first stream:

node stream-separation.mjs 1>/dev/null
12 records processed
process exiting with code 0

This separation is the precondition for the tool to be pluggable into a pipeline. If the counter line went to standard output, the next command reading that output would mistake it for a data line.

console.log writes to standard output, console.error to standard error. Both are convenience functions built on write; formatting objects readably is useful, but where the output feeds another program, write is preferred for controlling the format directly.

Exit Code

The only numeric result a process reports to its caller is the exit code. The rule from the Shell Programming course holds here too: zero means success, any nonzero value is a kind of failure.

There are two ways to set the code, and the difference between them matters.

// exit-hard.mjs
setTimeout(() => console.log('pending work'), 0);
console.log('before exit');
process.exit(3);
node exit-hard.mjs; echo "exit code: $?"
before exit
exit code: 3
// exit-soft.mjs
setTimeout(() => console.log('pending work'), 0);
console.log('before exit');
process.exitCode = 3;
node exit-soft.mjs; echo "exit code: $?"
before exit
pending work
exit code: 3

process.exit stops the event loop right then; queued callbacks never run — the absence of pending work in the first output above is the result. The same interruption can apply to output not yet flushed: in a process whose output is connected to a file or pipe, the write can run asynchronously, and a hard exit can cut it off midway.

Writing to process.exitCode, though, only records the result. The process ends with this code once the loop finishes its work and empties naturally. A pending write, a closing connection, and an ongoing file operation get to complete.

The rule: a hard exit is used only when the process’s state is judged to be corrupted. On ordinary error paths, the code is written to the field and the process ends on its own.

The tool above follows this rule. When the file is not found, the process is given code 66, but it is not cut short; the counter line and the exit hook still run:

node stream-separation.mjs missing.ndjson; echo "exit code: $?"
error: could not read missing.ndjson (ENOENT)
0 records processed
process exiting with code 66
exit code: 66

The Exit Hook

The callback set up with process.on('exit', ...) runs right before the process ends and takes the exit code as its argument — what wrote the last line in both runs above.

This hook has one strict rule: asynchronous work cannot be started inside it. The process is about to close; the loop is never returned to after the hook, so a timer or file write set up inside it never runs. The hook is only for short, synchronous work — writing a counter, releasing a lock, that sort of thing.

Real cleanup work is done when a shutdown signal is received; signals and graceful shutdown are the subject of the Process Management and Robustness lesson.

Two more fields describe the process itself. process.pid is the identifier the operating system gives it, different on every run, used to tell processes apart in log lines. process.uptime() gives the seconds since the process started, written as age information at health probes.

Summary

  • The first two elements of process.argv are the executable and main-module paths; user arguments start at the third element.
  • process.env values are always strings; numeric or boolean interpretation is the reading code’s responsibility.
  • The three standard streams are offered as objects; a tool becomes pluggable into a pipeline once data goes to standard output and diagnostics go to standard error.
  • process.exit cuts the loop right then and drops pending work; process.exitCode only records the result and lets the process end naturally.
  • The exit hook is suited only to synchronous work; since the event loop is never returned to after it, asynchronous cleanup cannot be done there.

Next Step

In this lesson, the file name was given as measurements.ndjson, relative to the working directory; the program cannot find it when called from another directory. The next lesson takes up the mechanism that removes this dependency: the rules the runtime uses to decide which file to load when a name is written — the distinction between a built-in module, a file path, and a package name.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close