make-promises-safe
A node.js module to make the use of promises safe.
It implements:
- the deprecation DEP0018 of Node.js in versions >= 6, as
using Promises without this module might cause file descriptor and memory
leak; follow this for more details.
- exiting on multiple resolve or reject, as those could hide bugs that
will be completely silent; follow
this for more details.
Install
npm install make-promises-safe --save
Usage
'use strict'
require('make-promises-safe')
const http = require('http')
const server = http.createServer(handle)
server.listen(3000)
function handle (req, res) {
doStuff()
.then((body) => {
res.end(body)
})
}
function doStuff () {
if (Math.random() < 0.5) {
return Promise.reject(new Error('kaboom'))
}
return Promise.resolve('hello world')
}
as a preloader
You can add this behavior to any Node.js application by using it as a
preloader:
node -r make-promises-safe server.js
with core dumps
You can also create a core dump when an unhandled rejection occurs:
require('make-promises-safe').abort = true
The 'unhandledRejection' problem
Node.js crashes if there is an uncaught exception, while it does not
crash if there is an 'unhandledRejection'
, i.e. a Promise without a
.catch()
handler.
If you are using promises, you should attach a .catch()
handler
synchronously.
As an example, the following server will leak a file descriptor because
of a missing .catch()
handler:
const http = require('http')
const server = http.createServer(handle)
server.listen(3000)
function handle (req, res) {
doStuff()
.then((body) => {
res.end(body)
})
}
function doStuff () {
if (Math.random() < 0.5) {
return Promise.reject(new Error('kaboom'))
}
return Promise.resolve('hello world')
}
The Solution
make-promises-safe
installs an process.on('unhandledRejection')
handler that prints the stacktrace and exits the process with an exit
code of 1, just like any uncaught exception.
Promises that resolves or rejects multiple times
Promises whose resolve
or reject
functions are called multiple times
might cause memory or file descriptor leaks, as resource might not be
consumed correctly.
As an example, the following code will error with a EMFILE:
const http = require('http')
const fs = require('fs')
const stream = require('stream')
const { promisify } = require('util')
const pipeline = promisify(stream.pipeline)
const server = http.createServer(handle)
server.listen(3000)
function handle (req, res) {
openFile()
.then((body) => {
return pipeline(body, res)
})
.catch((err) => {
res.statusCode = 500
res.end(err.toString())
})
}
function openFile () {
return new Promise(function (resolve, reject) {
if (Math.random() < 0.5) {
reject(new Error('kaboom'))
}
resolve(fs.createReadStream(__filename))
})
}
Under load, the above script will exits with an EMFILE error that would
be extremely hard to debug.
The Solution
make-promises-safe
installs an process.on('multipleResolves')
handler that prints the type of event, the promise and the reason
(the value that resolve
or reject
were called with)
and exits the process with an exit code of 1.
This feature is available only on Node >= 10.12.0.
License
MIT