make-promises-safe
Advanced tools
Comparing version 2.0.0 to 3.0.0
'use strict' | ||
const event = 'unhandledRejection' | ||
function install (event, cb) { | ||
if (process.listenerCount(event) !== 0) { | ||
return | ||
} | ||
if (process.listenerCount(event) === 0) { | ||
setup() | ||
process.on(event, cb) | ||
} | ||
function setup () { | ||
process.on(event, function (err) { | ||
console.error(err) | ||
if (module.exports.abort) { | ||
process.abort() | ||
} | ||
process.exit(1) | ||
}) | ||
install('unhandledRejection', function (err) { | ||
console.error(err) | ||
abortOrExit() | ||
}) | ||
install('multipleResolves', function (type, promise, reason) { | ||
console.error(type, promise, reason) | ||
abortOrExit() | ||
}) | ||
function abortOrExit () { | ||
if (module.exports.abort) { | ||
process.abort() | ||
} | ||
process.exit(1) | ||
} | ||
module.exports.abort = false |
{ | ||
"name": "make-promises-safe", | ||
"version": "2.0.0", | ||
"version": "3.0.0", | ||
"description": "Crash or abort if you get an unhandledRejection or multipleResolves", | ||
@@ -29,2 +29,3 @@ "main": "make-promises-safe.js", | ||
"pre-commit": "^1.2.2", | ||
"semver": "^5.6.0", | ||
"snazzy": "^7.0.0", | ||
@@ -31,0 +32,0 @@ "standard": "^10.0.2", |
129
README.md
# make-promises-safe [![Build Status](https://travis-ci.org/mcollina/make-promises-safe.svg?branch=master)](https://travis-ci.org/mcollina/make-promises-safe) | ||
A node.js module to make the use of promises safe. | ||
It implements the deprecation | ||
[DEP0018][unhandled] of Node.js in versions 6, 7 and 8. | ||
Using Promises without this module might cause file descriptor and memory | ||
leaks. | ||
It implements: | ||
## The Problem | ||
1. the deprecation [DEP0018][unhandled] of Node.js in versions >= 6, as | ||
using Promises without this module might cause file descriptor and memory | ||
leak; follow [this](#the-unhandledrejection-problem) for more details. | ||
2. exiting on multiple resolve or reject, as those could hide bugs that | ||
will be completely silent; follow | ||
[this](#promises-that-resolves-or-rejects-multiple-times) for more details. | ||
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. | ||
## Install | ||
**If you are using promises, you should attach a `.catch()` handler | ||
synchronously**. | ||
``` | ||
npm install make-promises-safe --save | ||
``` | ||
As an example, the following server will leak a file descriptor because | ||
of a missing `.catch()` handler: | ||
## Usage | ||
```js | ||
'use strict' | ||
require('make-promises-safe') // installs an 'unhandledRejection' handler | ||
const http = require('http') | ||
@@ -43,20 +46,33 @@ const server = http.createServer(handle) | ||
## The Solution | ||
### as a preloader | ||
`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. | ||
You can add this behavior to any Node.js application by using it as a | ||
preloader: | ||
## Install | ||
``` | ||
node -r make-promises-safe server.js | ||
``` | ||
### with core dumps | ||
You can also create a core dump when an unhandled rejection occurs: | ||
``` | ||
npm install make-promises-safe --save | ||
require('make-promises-safe').abort = true | ||
``` | ||
## Usage | ||
<a name="unhandledRejection"></a> | ||
## 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: | ||
```js | ||
'use strict' | ||
require('make-promises-safe') // installs an 'unhandledRejection' handler | ||
const http = require('http') | ||
@@ -83,19 +99,66 @@ const server = http.createServer(handle) | ||
### as a preloader | ||
### The Solution | ||
You can add this behavior to any Node.js application by using it as a | ||
preloader: | ||
`make-promises-safe` installs an [`process.on('unhandledRejection')`][unhandled-event] | ||
handler that prints the stacktrace and exits the process with an exit | ||
code of 1, just like any uncaught exception. | ||
<a name="multipleResolves"></a> | ||
## 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: | ||
```js | ||
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) { | ||
// this simulates some other parts of the codebase that | ||
// could throw an error | ||
if (Math.random() < 0.5) { | ||
// note that a deveveloper forgot to return | ||
reject(new Error('kaboom')) | ||
} | ||
// we are creating a file even if the promise errored, | ||
// and it is never consumed or closed | ||
resolve(fs.createReadStream(__filename)) | ||
}) | ||
} | ||
``` | ||
node -r make-promises-safe server.js | ||
``` | ||
### with core dumps | ||
Under load, the above script will exits with an EMFILE error that would | ||
be extremely hard to debug. | ||
You can also create a core dump when an unhandled rejection occurs: | ||
### The Solution | ||
`make-promises-safe` installs an [`process.on('multipleResolves')`][multiple-event] | ||
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. | ||
``` | ||
require('make-promises-safe').abort = true | ||
``` | ||
This feature is available only on Node >= 10.12.0. | ||
## License | ||
@@ -106,1 +169,3 @@ | ||
[unhandled]: https://nodejs.org/dist/latest-v8.x/docs/api/deprecations.html#deprecations_dep0018_unhandled_promise_rejections | ||
[unhandled-event]: https://nodejs.org/dist/latest-v10.x/docs/api/process.html#process_event_unhandledrejection | ||
[multiple-event]: https://nodejs.org/dist/latest-v10.x/docs/api/process.html#process_event_multipleresolves |
36
test.js
@@ -6,3 +6,6 @@ 'use strict' | ||
const spawn = require('child_process').spawn | ||
const semver = require('semver') | ||
const hasMultipleResolves = semver.satisfies(process.version, '>= 10.12.0') | ||
const main = path.join(__dirname, require('./package').main) | ||
@@ -27,1 +30,34 @@ | ||
}) | ||
test('crashes the process on double resolve', { skip: !hasMultipleResolves }, (t) => { | ||
t.plan(2) | ||
const child = spawn(process.execPath, [ | ||
'-r', main, '-e', 'new Promise((r) => { r(); r(\'a\') })']) | ||
child.stderr.on('data', function (chunk) { | ||
const expected = 'resolve Promise { undefined } a' | ||
t.ok(chunk.toString().trim().indexOf(expected.trim()) === 0) | ||
}) | ||
child.on('close', (code) => { | ||
t.is(code, 1) | ||
}) | ||
}) | ||
test('crashes the process on reject after resolve', { skip: !hasMultipleResolves }, (t) => { | ||
t.plan(2) | ||
const child = spawn(process.execPath, [ | ||
'-r', main, '-e', 'new Promise((resolve, reject) => { resolve(); reject(new Error(\'kaboom\')) })']) | ||
child.stderr.on('data', function (chunk) { | ||
const expected = `reject Promise { undefined } Error: kaboom | ||
at Promise ([eval]:1:54)` | ||
t.ok(chunk.toString().trim().indexOf(expected.trim()) === 0) | ||
}) | ||
child.on('close', (code) => { | ||
t.is(code, 1) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
9571
8
110
169
5
1
3