In this article, we’ll take a look at the Node.jsNode.js is an asynchronous event-driven JavaScript runtime and is the most effective when building scalable network applications. Node.js is free of locks, so there's no chance to dead-lock any process. 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 shutdownThere are two ways to turn off a computer: by graceful shutdown, or hard shutdown. Graceful shutdown means that the OS (operating system) can safely shut down its processes and close all connections, however long that takes. This helps to prevent accidental data loss or other unexpected problems if the shutdown is performed intentionally by the user., and you’ll handle errors much more efficiently.
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 uncaughtException
s – 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:
- The applications get notified to stop (received
SIGTERM
). - The applications notify the load balancers that they aren’t ready for newer requests.
- The applications finish all the ongoing requests.
- Then, it releases all of the resources (like a database connection) correctly.
- 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 asyncAsynchrony, in software programming, refers to events that occur outside of the primary program flow and methods for dealing with them. External events such as signals or activities prompted by a program that occur at the same time as program execution without causing the program to block and wait for results are examples of this category. Asynchronous input/output is an... operations are in progress,
- as writing to
STDOUT
andSTDERR
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 anuncaughtException
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 to deliver trainings for those who are interested in working with Node.js. Check out the Beginner Node.js Training Agenda here.