In this article, we'll take a look at the Node.js Process module, and what hidden gems it has to offer. After you’ve read this post, you’ll be able to write production-ready applications with much more confidence. You’ll know what process states your Node.js apps will have, you’ll be able to do graceful shutdown, and you’ll handle errors much more efficiently.

In the new Mastering the Node.js Core Modules series you can learn what hidden/barely known features the core modules have, and how you can use them. By going through the basic elements of these Node.js modules, you’ll have a better understanding of they work and how can you eliminate any errors.

In this chapter, we’ll take a look at the Node.js process module. The process object (which is an instance of the EventEmitter) is a global variable that provides information on the currently running Node.js process.

Events to watch out for in the Node.js process module

As the process module is an EventEmitter, you can subscribe to its events just like you do it with any other instances of the EventEmitter using the .on call:

process.on('eventName', () => {
  //do something
})

uncaughtException

This event is emitted when an uncaught JavaScript exception bubbles back to the event loop.

By default, if no event listeners are added to the uncaughtException handler, the process will print the stack trace to stderr and exit. If you add an event listener, you change this behavior to the one you implement in your listener:

process.on('uncaughtException', (err) => {
  // here the 1 is a file descriptor for STDERR
  fs.writeSync(1, `Caught exception: ${err}\n`)
})

During the past years, we have seen this event used in many wrong ways. The most important advice when using the uncaughtException event in the process module are the following:

  • if uncaughtException happens, your application is in an undefined state,
  • recovering from uncaughtException is strongly discouraged, it is not safe to continue normal operation after it,
  • the handler should only be used for synchronous cleanup of allocated resources,
  • exceptions thrown in this handler are not caught, and the application will exit immediately,
  • you should always monitor your process with an external tool, and restart it when needed (for example, when it crashes).

unhandledRejection

Imagine you have the following code snippet:

const fs = require('fs-extra')

fs.copy('/tmp/myfile', '/tmp/mynewfile')
  .then(() => console.log('success!'))

What would happen if the source file didn’t exist? Well, the answer depends on the Node.js version you are running. In some of them (mostly version 4 and below), the process would silently fail, and you would just sit there wondering what happened.

In more recent Node.js versions, you would get the following error message:

(node:28391) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): undefined
(node:28391) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

This means that we missed the error handling in the Promise we used for copying a file. The example should have been written this way:

fs.copy('/tmp/myfile', '/tmp/mynewfile')
  .then(() => console.log('success!'))
  .catch(err => console.error(err))

The problem with not handling Promise rejections is the same as in the case of uncaughtExceptions - your Node.js process will be in an unknown state. What is even worse, is that it might cause file descriptor failure and memory leaks. Your best course of action in this scenario is to restart the Node.js process.

To do so, you have to attach an event listener to the unhandledRejection event, and exit the process with process.exit(1).

Our recommendation here is to go with Matteo Collina's make-promises-safe package, which solves it for you out of the box.

Node.js Signal Events

Signal events will also be emitted when Node.js receives POSIX signal events. Let's take a look at the two most important ones, SIGTERM and SIGUSR1.

You can find the full list of supported signals here.

SIGTERM

The SIGTERM signal is sent to a Node.js process to request its termination. Unlike the SIGKILL signal, it can be listened on or ignored by the process.

This allows the process to be shut down in a nice manner, by releasing the resources it allocated, like file handlers or database connections. This way of shutting down applications is called graceful shutdown.

Essentially, the following steps need to happen before performing a graceful shutdown:

  1. The applications get notified to stop (received SIGTERM).
  2. The applications notify the load balancers that they aren’t ready for newer requests.
  3. The applications finish all the ongoing requests.
  4. Then, it releases all of the resources (like a database connection) correctly.
  5. The application exits with a "success" status code (process.exit()).

Read this article for more on graceful shutdown in Node.js.

SIGUSR1

By the POSIX standard, SIGUSR1 and SIGUSR2 can be used for user-defined conditions. Node.js chose to use this event to start the built-in debugger.

You can send the SIGUSR1 signal to the process by running the following command:

kill -USR1 PID_OF_THE_NODE_JS_PROCESS

Once you did that, the Node.js process in question will let you know that the debugger is running:

Starting debugger agent.
Debugger listening on [::]:5858

Methods and values exposed by the Node.js process module

process.cwd()

This method returns the current working directory for the running Node.js process.

$ node -e 'console.log(`Current directory: ${process.cwd()}`)'
Current directory: /Users/gergelyke/Development/risingstack/risingsite_v2

In case you have to change it, you can do so by calling process.chdir(path).

process.env

This property returns an object containing the user environment, just like environ.

If you are building applications that conform the 12-factor application principles, you will heavily depend on it; as the third principle of a twelve-factor application requires that all configurations should be stored in the user environment.

Environment variables are preferred, as it is easy to change them between deploys without changing any code. Unlike config files, there is little chance of them being accidentally checked into the code repository.

It is worth mentioning, that you can change the values of the process.env object, however, it won't be reflected in the user environment.

process.exit([code])

This method tells the Node.js process to terminate the process synchronously with an exit status code. Important consequences of this call::

  • it will force the process to exit as quickly as possible
    • even if some async operations are in progress,
    • as writing to STDOUT and STDERR is async, some logs can be lost
  • in most cases, it is not recommended to use process.exit() - instead, you can let it shut down by depleting the event loop.

process.kill(pid, [signal])

With this method, you can send any POSIX signals to any processes. You don’t only kill processes as the name suggest - this command acts as a signal sender too (like the kill system call.)

Exit codes used by Node.js

If all goes well, Node.js will exit with the exit code 0. However, if the process exits because of an error, you’ll get one of the following error codes::

  • 1 : Uncaught fatal exception, which was not handled by an uncaughtException handler,
  • 5 : Fatal error in V8 (like memory allocation failures),
  • 9 : Invalid argument, when an unknown option was specified, or an option which requires a value was set without the value.

These are just the most common exit codes, for all the exit codes, please refer to https://nodejs.org/api/process.html#process_exit_codes.

Learn more Node.js

These are the most important aspects of using the Node.js process module. We hope that by following the above listed advices, you’ll be able to get the most out of Node.js. In case you have any questions, don’t hesitate to reach out to us in the comments section below.

By studying the core modules, you can quickly get the hang of Node.js! Although, in case you feel that you could use some extra information about the foundations, or you have doubts about how you can implement Node successfully in your organization - we can help!

The team of RisingStack is going to travel around Europe this to hold trainings for those who are interested in working with Node.js. Check out the Beginner Node.js Training Agenda here.