-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
281 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,64 @@ | ||
[![downloads](https://img.shields.io/npm/dt/on-process-error.svg?logo=npm)](https://www.npmjs.com/package/on-process-error) [![last commit](https://img.shields.io/github/last-commit/autoserver-org/on-process-error.svg?logo=github)](https://github.com/autoserver-org/on-process-error/graphs/contributors) [![license](https://img.shields.io/github/license/autoserver-org/on-process-error.svg?logo=github)](https://www.apache.org/licenses/LICENSE-2.0) [![npm](https://img.shields.io/npm/v/on-process-error.svg?logo=npm)](https://www.npmjs.com/package/on-process-error) [![node](https://img.shields.io/node/v/on-process-error.svg?logo=node.js)](#) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg?logo=javascript)](https://standardjs.com) [![eslint-config-standard-prettier-fp](https://img.shields.io/badge/eslint-config--standard--prettier--fp-green.svg?logo=eslint)](https://github.com/autoserver-org/eslint-config-standard-prettier-fp) | ||
|
||
Add an event listener to handle any process errors: | ||
|
||
- [`uncaughtException`](https://nodejs.org/api/process.html#process_event_uncaughtexception): an exception was thrown and not caught | ||
- [`unhandledRejection`](https://nodejs.org/api/process.html#process_event_unhandledrejection): a promise was rejected and not handled | ||
- [`rejectionHandled`](https://nodejs.org/api/process.html#process_event_rejectionhandled): a promise was rejected and handled too late | ||
- [`multipleResolves`](https://nodejs.org/api/process.html#process_event_multipleresolves): a promise was resolved/rejected twice | ||
- [`warning`](https://nodejs.org/api/process.html#process_event_warning): a warning was produced using [`process.emitWarning()`](https://nodejs.org/api/process.html#process_process_emitwarning_warning_options) | ||
|
||
# Usage | ||
|
||
<!-- eslint-disable no-unused-vars, node/no-missing-require, | ||
import/no-unresolved, unicorn/filename-case, strict --> | ||
|
||
```js | ||
const onProcessError = require('on-process-error') | ||
|
||
const undoSetup = onProcessError.setup() | ||
``` | ||
|
||
When any process errors occur, it will be logged using `console.error()`: | ||
|
||
- the message will include detailed information about the error | ||
- for `warning`, `console.warn()` will be used instead. | ||
- for `uncaughtException`, [`process.exit(1)` will be called after | ||
`console.error()`](https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly). | ||
|
||
You can undo everything by firing the function returned by | ||
`onProcessError.setup()` (called `undoSetup` in the example above). | ||
|
||
# Custom handling | ||
|
||
You can override the default behavior by passing a custom function instead. | ||
|
||
<!-- eslint-disable no-empty-function, no-unused-vars, node/no-missing-require, | ||
import/no-unresolved, unicorn/filename-case, strict --> | ||
|
||
```js | ||
const onProcessError = require('on-process-error') | ||
|
||
const undoSetup = onProcessError.setup( | ||
({ eventName, promiseState, promiseValue, error, message }) => {}, | ||
) | ||
``` | ||
|
||
The function's argument is an object with the following properties: | ||
|
||
- `eventName` `{string}`: can be `uncaughtException`, `unhandledRejection`, | ||
`rejectionHandled`, `multipleResolves` or `warning` | ||
- `promiseState` `{string}`: whether promise was `resolved` or `rejected`. | ||
For `unhandledRejection`, `rejectionHandled` and `multipleResolves`. | ||
- `promiseValue` `{any}`: value resolved/rejected by the promise. | ||
For `unhandledRejection`, `rejectionHandled` and `multipleResolves`. | ||
- `error` `{error}`: | ||
- can be: | ||
- thrown by `uncaughtException` | ||
- emitted by `warning`. [`error.name`, `error.code` and `error.detail`](https://nodejs.org/api/process.html#process_event_warning) | ||
might be defined. | ||
- rejected by `unhandledRejection`, `rejectionHandled` or | ||
`multipleResolves`'s promise (if the promise was rejected). | ||
- if the error is not an `Error` instance (e.g. if it is a string), it will | ||
be normalized to one using `new Error()`. | ||
- `message` `{string}`: detailed message summing up all of the above. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
{ | ||
"JAVASCRIPT": ["*.js", "gulp/**/*.js"], | ||
"JAVASCRIPT": ["*.js", "src/**/*.js", "gulp/**/*.js"], | ||
"MARKDOWN": ["*.md"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,3 @@ | ||
// eslint-disable-next-line filenames/match-exported | ||
'use strict' | ||
|
||
// eslint-disable-next-line no-empty-function | ||
const onProcessError = function() {} | ||
|
||
module.exports = onProcessError | ||
module.exports = require('./src') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
"version": "0.1.1", | ||
"main": "index.js", | ||
"files": [ | ||
"src", | ||
"!*~" | ||
], | ||
"scripts": { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
'use strict' | ||
|
||
const { exit } = require('process') | ||
|
||
// Default event handler | ||
const defaultHandler = function({ eventName, message }) { | ||
const level = eventName === 'warning' ? 'warn' : 'error' | ||
// eslint-disable-next-line no-restricted-globals, no-console | ||
console[level](message) | ||
|
||
// See https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly | ||
if (eventName === 'uncaughtException') { | ||
exit(1) | ||
} | ||
} | ||
|
||
module.exports = { | ||
defaultHandler, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
'use strict' | ||
|
||
// Retrieve `error` | ||
const getError = function({ error, promiseState, promiseValue }) { | ||
// `error` should always be defined for `uncaughtException` and `warning`. | ||
// The other events are promise-based, in which case it should only be defined | ||
// if the promise was rejected | ||
if (promiseState === 'resolved') { | ||
return | ||
} | ||
|
||
const errorA = promiseState === 'rejected' ? promiseValue : error | ||
|
||
// Throwing `undefined` (`uncaughtException`) or rejecting a promise with | ||
// `undefined` is improper, so we normalize it to an `Error` instance | ||
const errorB = errorA === undefined ? 'undefined' : errorA | ||
|
||
// Normalize error if it's not an `Error` (as it should) | ||
const errorC = new Error(`Error: ${errorB}`) | ||
return errorC | ||
} | ||
|
||
module.exports = { | ||
getError, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
'use strict' | ||
|
||
const { handleEvent } = require('./handle') | ||
|
||
// List of all handled events | ||
// Each event must pass its related `error` or `promise` to the generic | ||
// `handleEvent()` | ||
const uncaughtException = function(context, error) { | ||
handleEvent({ ...context, error }) | ||
} | ||
|
||
const warning = function(context, error) { | ||
handleEvent({ ...context, error }) | ||
} | ||
|
||
const unhandledRejection = function(context, promiseValue, promise) { | ||
handleEvent({ ...context, promise }) | ||
} | ||
|
||
const rejectionHandled = function(context, promise) { | ||
handleEvent({ ...context, promise }) | ||
} | ||
|
||
// eslint-disable-next-line no-inline-comments | ||
const multipleResolves = function(context, type, promise /*, promiseValue */) { | ||
handleEvent({ ...context, promise }) | ||
} | ||
|
||
module.exports = { | ||
uncaughtException, | ||
warning, | ||
unhandledRejection, | ||
rejectionHandled, | ||
multipleResolves, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
'use strict' | ||
|
||
const { getError } = require('./error') | ||
const { getMessage } = require('./message') | ||
|
||
// Generic event handler for all events. | ||
const handleEvent = async function({ handlerFunc, eventName, error, promise }) { | ||
const { promiseState, promiseValue } = await parsePromise({ promise }) | ||
const errorA = getError({ error, promiseValue }) | ||
const message = getMessage({ | ||
eventName, | ||
promiseState, | ||
promiseValue, | ||
error: errorA, | ||
}) | ||
|
||
handlerFunc({ eventName, promiseState, promiseValue, error: errorA, message }) | ||
} | ||
|
||
// Retrieve promise's resolved/rejected state and value. | ||
const parsePromise = async function({ promise }) { | ||
// `uncaughtException` and `warning` events do not have `promise`. | ||
if (promise === undefined) { | ||
return {} | ||
} | ||
|
||
try { | ||
const promiseValue = await promise | ||
return { promiseState: 'resolved', promiseValue } | ||
} catch (error) { | ||
return { promiseState: 'rejected', promiseValue: error } | ||
} | ||
} | ||
|
||
module.exports = { | ||
handleEvent, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
'use strict' | ||
|
||
module.exports = { | ||
...require('./main'), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
'use strict' | ||
|
||
const process = require('process') | ||
|
||
const { defaultHandler } = require('./default') | ||
const EVENTS = require('./events') | ||
|
||
// Add event handling for all process-related errors | ||
const setup = function(handlerFunc = defaultHandler) { | ||
const listeners = addListeners({ handlerFunc }) | ||
const removeAll = removeListeners.bind(null, listeners) | ||
return removeAll | ||
} | ||
|
||
const addListeners = function({ handlerFunc }) { | ||
return Object.entries(EVENTS).map((eventName, eventFunc) => | ||
addListener({ handlerFunc, eventName, eventFunc }), | ||
) | ||
} | ||
|
||
const addListener = function({ handlerFunc, eventName, eventFunc }) { | ||
const eventListener = eventFunc.bind(null, { handlerFunc, eventName }) | ||
process.on(eventName, eventListener) | ||
|
||
return { eventListener, eventName } | ||
} | ||
|
||
// Remove all event handlers | ||
const removeListeners = function(listeners) { | ||
listeners.forEach(removeListener) | ||
} | ||
|
||
const removeListener = function({ eventListener, eventName }) { | ||
// TODO: use `process.off()` instead of `process.removeListener()` | ||
// after dropping Node.js <10 support | ||
process.removeListener(eventName, eventListener) | ||
} | ||
|
||
module.exports = { | ||
setup, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
'use strict' | ||
|
||
// Retrieve `message` which sums up all information that can be gathered about | ||
// the event. | ||
const getMessage = function({ eventName, promiseState, promiseValue, error }) { | ||
const mainMessage = MAIN_MESSAGES[eventName] | ||
const warningMessage = getWarningMessage({ eventName, error }) | ||
const promiseMessage = getPromiseMessage({ promiseState, promiseValue }) | ||
|
||
const message = [mainMessage, warningMessage, promiseMessage, error] | ||
.filter(part => part !== undefined) | ||
.join('\n') | ||
return message | ||
} | ||
|
||
// First line of `message` | ||
const MAIN_MESSAGES = { | ||
uncaughtException: 'Uncaught exception:', | ||
warning: 'Warning:', | ||
unhandledRejection: 'A promise was rejected but not handled:', | ||
rejectionHandled: 'A promise was handled after being already rejected:', | ||
multipleResolves: 'A promise was resolved/rejected multiple times:', | ||
} | ||
|
||
// `warning` events use `Error.name|code|detail` in `message` | ||
const getWarningMessage = function({ | ||
eventName, | ||
error: { name, code, detail } = {}, | ||
}) { | ||
if (eventName !== 'warning') { | ||
return | ||
} | ||
|
||
const codeMessage = code === undefined ? '' : ` (${code})` | ||
const detailMessage = detail === undefined ? '' : ` ${detail}` | ||
const warningMessage = `${name}${codeMessage}${detailMessage}` | ||
return warningMessage | ||
} | ||
|
||
// `unhandledRejection`, `rejectionHandled` and `multipleResolves` events show | ||
// the promise's resolved/rejected state and value in `message` | ||
const getPromiseMessage = function({ promiseState, promiseValue }) { | ||
if (promiseState === undefined) { | ||
return | ||
} | ||
|
||
const value = promiseValue instanceof Error ? 'an error' : promiseValue | ||
return `Promise was ${promiseState} with ${value}` | ||
} | ||
|
||
module.exports = { | ||
getMessage, | ||
} |