Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v12.x backport] events: allow monitoring error events #32004

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions doc/api/events.md
Expand Up @@ -155,6 +155,18 @@ myEmitter.emit('error', new Error('whoops!'));
// Prints: whoops! there was an error
```

It is possible to monitor `'error'` events without consuming the emitted error
by installing a listener using the symbol `errorMonitor`.

```js
const myEmitter = new MyEmitter();
myEmitter.on(EventEmitter.errorMonitor, (err) => {
MyMonitoringTool.log(err);
});
myEmitter.emit('error', new Error('whoops!'));
// Still throws and crashes Node.js
```

## Capture Rejections of Promises

> Stability: 1 - captureRejections is experimental.
Expand Down Expand Up @@ -348,6 +360,19 @@ the event emitter instance, the event’s name and the number of attached
listeners, respectively.
Its `name` property is set to `'MaxListenersExceededWarning'`.

### `EventEmitter.errorMonitor`
<!-- YAML
added: REPLACEME
-->

This symbol shall be used to install a listener for only monitoring `'error'`
events. Listeners installed using this symbol are called before the regular
`'error'` listeners are called.

Installing a listener using this symbol does not change the behavior once an
`'error'` event is emitted, therefore the process will still crash if no
regular `'error'` listener is installed.

### `emitter.addListener(eventName, listener)`
<!-- YAML
added: v0.1.26
Expand Down
9 changes: 7 additions & 2 deletions lib/events.js
Expand Up @@ -59,6 +59,7 @@ const {
} = require('internal/util/inspect');

const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');

function EventEmitter(opts) {
EventEmitter.init.call(this, opts);
Expand Down Expand Up @@ -88,6 +89,8 @@ ObjectDefineProperty(EventEmitter, 'captureRejections', {
enumerable: true
});

EventEmitter.errorMonitor = kErrorMonitor;

// The default for captureRejections is false
ObjectDefineProperty(EventEmitter.prototype, kCapture, {
value: false,
Expand Down Expand Up @@ -261,9 +264,11 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
let doError = (type === 'error');

const events = this._events;
if (events !== undefined)
if (events !== undefined) {
if (doError && events[kErrorMonitor] !== undefined)
this.emit(kErrorMonitor, ...args);
doError = (doError && events.error === undefined);
else if (!doError)
} else if (!doError)
return false;

// If there is no 'error' event listener then throw.
Expand Down
32 changes: 32 additions & 0 deletions test/parallel/test-event-emitter-error-monitor.js
@@ -0,0 +1,32 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const EventEmitter = require('events');

const EE = new EventEmitter();
const theErr = new Error('MyError');

EE.on(
EventEmitter.errorMonitor,
common.mustCall(function onErrorMonitor(e) {
assert.strictEqual(e, theErr);
}, 3)
);

// Verify with no error listener
assert.throws(
() => EE.emit('error', theErr), theErr
);

// Verify with error listener
EE.once('error', common.mustCall((e) => assert.strictEqual(e, theErr)));
EE.emit('error', theErr);


// Verify it works with once
process.nextTick(() => EE.emit('error', theErr));
assert.rejects(EventEmitter.once(EE, 'notTriggered'), theErr);

// Only error events trigger error monitor
EE.on('aEvent', common.mustCall());
EE.emit('aEvent');