Skip to content

Commit 8768199

Browse files
committed
Add initial code
1 parent cee041d commit 8768199

File tree

11 files changed

+281
-6
lines changed

11 files changed

+281
-6
lines changed

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,64 @@
11
[![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)
2+
3+
Add an event listener to handle any process errors:
4+
5+
- [`uncaughtException`](https://nodejs.org/api/process.html#process_event_uncaughtexception): an exception was thrown and not caught
6+
- [`unhandledRejection`](https://nodejs.org/api/process.html#process_event_unhandledrejection): a promise was rejected and not handled
7+
- [`rejectionHandled`](https://nodejs.org/api/process.html#process_event_rejectionhandled): a promise was rejected and handled too late
8+
- [`multipleResolves`](https://nodejs.org/api/process.html#process_event_multipleresolves): a promise was resolved/rejected twice
9+
- [`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)
10+
11+
# Usage
12+
13+
<!-- eslint-disable no-unused-vars, node/no-missing-require,
14+
import/no-unresolved, unicorn/filename-case, strict -->
15+
16+
```js
17+
const onProcessError = require('on-process-error')
18+
19+
const undoSetup = onProcessError.setup()
20+
```
21+
22+
When any process errors occur, it will be logged using `console.error()`:
23+
24+
- the message will include detailed information about the error
25+
- for `warning`, `console.warn()` will be used instead.
26+
- for `uncaughtException`, [`process.exit(1)` will be called after
27+
`console.error()`](https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly).
28+
29+
You can undo everything by firing the function returned by
30+
`onProcessError.setup()` (called `undoSetup` in the example above).
31+
32+
# Custom handling
33+
34+
You can override the default behavior by passing a custom function instead.
35+
36+
<!-- eslint-disable no-empty-function, no-unused-vars, node/no-missing-require,
37+
import/no-unresolved, unicorn/filename-case, strict -->
38+
39+
```js
40+
const onProcessError = require('on-process-error')
41+
42+
const undoSetup = onProcessError.setup(
43+
({ eventName, promiseState, promiseValue, error, message }) => {},
44+
)
45+
```
46+
47+
The function's argument is an object with the following properties:
48+
49+
- `eventName` `{string}`: can be `uncaughtException`, `unhandledRejection`,
50+
`rejectionHandled`, `multipleResolves` or `warning`
51+
- `promiseState` `{string}`: whether promise was `resolved` or `rejected`.
52+
For `unhandledRejection`, `rejectionHandled` and `multipleResolves`.
53+
- `promiseValue` `{any}`: value resolved/rejected by the promise.
54+
For `unhandledRejection`, `rejectionHandled` and `multipleResolves`.
55+
- `error` `{error}`:
56+
- can be:
57+
- thrown by `uncaughtException`
58+
- emitted by `warning`. [`error.name`, `error.code` and `error.detail`](https://nodejs.org/api/process.html#process_event_warning)
59+
might be defined.
60+
- rejected by `unhandledRejection`, `rejectionHandled` or
61+
`multipleResolves`'s promise (if the promise was rejected).
62+
- if the error is not an `Error` instance (e.g. if it is a string), it will
63+
be normalized to one using `new Error()`.
64+
- `message` `{string}`: detailed message summing up all of the above.

gulp/files.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"JAVASCRIPT": ["*.js", "gulp/**/*.js"],
2+
"JAVASCRIPT": ["*.js", "src/**/*.js", "gulp/**/*.js"],
33
"MARKDOWN": ["*.md"]
44
}

index.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
// eslint-disable-next-line filenames/match-exported
21
'use strict'
32

4-
// eslint-disable-next-line no-empty-function
5-
const onProcessError = function() {}
6-
7-
module.exports = onProcessError
3+
module.exports = require('./src')

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"version": "0.1.1",
44
"main": "index.js",
55
"files": [
6+
"src",
67
"!*~"
78
],
89
"scripts": {

src/default.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict'
2+
3+
const { exit } = require('process')
4+
5+
// Default event handler
6+
const defaultHandler = function({ eventName, message }) {
7+
const level = eventName === 'warning' ? 'warn' : 'error'
8+
// eslint-disable-next-line no-restricted-globals, no-console
9+
console[level](message)
10+
11+
// See https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly
12+
if (eventName === 'uncaughtException') {
13+
exit(1)
14+
}
15+
}
16+
17+
module.exports = {
18+
defaultHandler,
19+
}

src/error.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict'
2+
3+
// Retrieve `error`
4+
const getError = function({ error, promiseState, promiseValue }) {
5+
// `error` should always be defined for `uncaughtException` and `warning`.
6+
// The other events are promise-based, in which case it should only be defined
7+
// if the promise was rejected
8+
if (promiseState === 'resolved') {
9+
return
10+
}
11+
12+
const errorA = promiseState === 'rejected' ? promiseValue : error
13+
14+
// Throwing `undefined` (`uncaughtException`) or rejecting a promise with
15+
// `undefined` is improper, so we normalize it to an `Error` instance
16+
const errorB = errorA === undefined ? 'undefined' : errorA
17+
18+
// Normalize error if it's not an `Error` (as it should)
19+
const errorC = new Error(`Error: ${errorB}`)
20+
return errorC
21+
}
22+
23+
module.exports = {
24+
getError,
25+
}

src/events.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict'
2+
3+
const { handleEvent } = require('./handle')
4+
5+
// List of all handled events
6+
// Each event must pass its related `error` or `promise` to the generic
7+
// `handleEvent()`
8+
const uncaughtException = function(context, error) {
9+
handleEvent({ ...context, error })
10+
}
11+
12+
const warning = function(context, error) {
13+
handleEvent({ ...context, error })
14+
}
15+
16+
const unhandledRejection = function(context, promiseValue, promise) {
17+
handleEvent({ ...context, promise })
18+
}
19+
20+
const rejectionHandled = function(context, promise) {
21+
handleEvent({ ...context, promise })
22+
}
23+
24+
// eslint-disable-next-line no-inline-comments
25+
const multipleResolves = function(context, type, promise /*, promiseValue */) {
26+
handleEvent({ ...context, promise })
27+
}
28+
29+
module.exports = {
30+
uncaughtException,
31+
warning,
32+
unhandledRejection,
33+
rejectionHandled,
34+
multipleResolves,
35+
}

src/handle.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict'
2+
3+
const { getError } = require('./error')
4+
const { getMessage } = require('./message')
5+
6+
// Generic event handler for all events.
7+
const handleEvent = async function({ handlerFunc, eventName, error, promise }) {
8+
const { promiseState, promiseValue } = await parsePromise({ promise })
9+
const errorA = getError({ error, promiseValue })
10+
const message = getMessage({
11+
eventName,
12+
promiseState,
13+
promiseValue,
14+
error: errorA,
15+
})
16+
17+
handlerFunc({ eventName, promiseState, promiseValue, error: errorA, message })
18+
}
19+
20+
// Retrieve promise's resolved/rejected state and value.
21+
const parsePromise = async function({ promise }) {
22+
// `uncaughtException` and `warning` events do not have `promise`.
23+
if (promise === undefined) {
24+
return {}
25+
}
26+
27+
try {
28+
const promiseValue = await promise
29+
return { promiseState: 'resolved', promiseValue }
30+
} catch (error) {
31+
return { promiseState: 'rejected', promiseValue: error }
32+
}
33+
}
34+
35+
module.exports = {
36+
handleEvent,
37+
}

src/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict'
2+
3+
module.exports = {
4+
...require('./main'),
5+
}

src/main.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict'
2+
3+
const process = require('process')
4+
5+
const { defaultHandler } = require('./default')
6+
const EVENTS = require('./events')
7+
8+
// Add event handling for all process-related errors
9+
const setup = function(handlerFunc = defaultHandler) {
10+
const listeners = addListeners({ handlerFunc })
11+
const removeAll = removeListeners.bind(null, listeners)
12+
return removeAll
13+
}
14+
15+
const addListeners = function({ handlerFunc }) {
16+
return Object.entries(EVENTS).map((eventName, eventFunc) =>
17+
addListener({ handlerFunc, eventName, eventFunc }),
18+
)
19+
}
20+
21+
const addListener = function({ handlerFunc, eventName, eventFunc }) {
22+
const eventListener = eventFunc.bind(null, { handlerFunc, eventName })
23+
process.on(eventName, eventListener)
24+
25+
return { eventListener, eventName }
26+
}
27+
28+
// Remove all event handlers
29+
const removeListeners = function(listeners) {
30+
listeners.forEach(removeListener)
31+
}
32+
33+
const removeListener = function({ eventListener, eventName }) {
34+
// TODO: use `process.off()` instead of `process.removeListener()`
35+
// after dropping Node.js <10 support
36+
process.removeListener(eventName, eventListener)
37+
}
38+
39+
module.exports = {
40+
setup,
41+
}

0 commit comments

Comments
 (0)