Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit fe21607

Browse filesBrowse files
jasnelltargos
authored andcommitted
events: add EventEmitterAsyncResource to core
Signd-off-by: James M Snell <jasnell@gmail.com> PR-URL: #41246 Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent c546cef commit fe21607
Copy full SHA for fe21607

File tree

Expand file treeCollapse file tree

3 files changed

+347
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

3 files changed

+347
-0
lines changed
Open diff view settings
Collapse file

‎doc/api/events.md‎

Copy file name to clipboardExpand all lines: doc/api/events.md
+85Lines changed: 85 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,89 @@ const emitter = new EventEmitter();
11661166
setMaxListeners(5, target, emitter);
11671167
```
11681168

1169+
## Class: `events.EventEmitterAsyncResource extends EventEmitter`
1170+
1171+
<!-- YAML
1172+
added: REPLACEME
1173+
-->
1174+
1175+
Integrates `EventEmitter` with {AsyncResource} for `EventEmitter`s that
1176+
require manual async tracking. Specifically, all events emitted by instances
1177+
of `events.EventEmitterAsyncResource` will run within its [async context][].
1178+
1179+
```js
1180+
const { EventEmitterAsyncResource } = require('events');
1181+
const { notStrictEqual, strictEqual } = require('assert');
1182+
const { executionAsyncId } = require('async_hooks');
1183+
1184+
// Async tracking tooling will identify this as 'Q'.
1185+
const ee1 = new EventEmitterAsyncResource({ name: 'Q' });
1186+
1187+
// 'foo' listeners will run in the EventEmitters async context.
1188+
ee1.on('foo', () => {
1189+
strictEqual(executionAsyncId(), ee1.asyncId);
1190+
strictEqual(triggerAsyncId(), ee1.triggerAsyncId);
1191+
});
1192+
1193+
const ee2 = new EventEmitter();
1194+
1195+
// 'foo' listeners on ordinary EventEmitters that do not track async
1196+
// context, however, run in the same async context as the emit().
1197+
ee2.on('foo', () => {
1198+
notStrictEqual(executionAsyncId(), ee2.asyncId);
1199+
notStrictEqual(triggerAsyncId(), ee2.triggerAsyncId);
1200+
});
1201+
1202+
Promise.resolve().then(() => {
1203+
ee1.emit('foo');
1204+
ee2.emit('foo');
1205+
});
1206+
```
1207+
1208+
The `EventEmitterAsyncResource` class has the same methods and takes the
1209+
same options as `EventEmitter` and `AsyncResource` themselves.
1210+
1211+
### `new events.EventEmitterAsyncResource(options)`
1212+
1213+
* `options` {Object}
1214+
* `captureRejections` {boolean} It enables
1215+
[automatic capturing of promise rejection][capturerejections].
1216+
**Default:** `false`.
1217+
* `name` {string} The type of async event. **Default::**
1218+
[`new.target.name`][].
1219+
* `triggerAsyncId` {number} The ID of the execution context that created this
1220+
async event. **Default:** `executionAsyncId()`.
1221+
* `requireManualDestroy` {boolean} If set to `true`, disables `emitDestroy`
1222+
when the object is garbage collected. This usually does not need to be set
1223+
(even if `emitDestroy` is called manually), unless the resource's `asyncId`
1224+
is retrieved and the sensitive API's `emitDestroy` is called with it.
1225+
When set to `false`, the `emitDestroy` call on garbage collection
1226+
will only take place if there is at least one active `destroy` hook.
1227+
**Default:** `false`.
1228+
1229+
### `eventemitterasyncresource.asyncId`
1230+
1231+
* Type: {number} The unique `asyncId` assigned to the resource.
1232+
1233+
### `eventemitterasyncresource.asyncResource`
1234+
1235+
* Type: The underlying {AsyncResource}.
1236+
1237+
The returned `AsyncResource` object has an additional `eventEmitter` property
1238+
that provides a reference to this `EventEmitterAsyncResource`.
1239+
1240+
### `eventemitterasyncresource.emitDestroy()`
1241+
1242+
Call all `destroy` hooks. This should only ever be called once. An error will
1243+
be thrown if it is called more than once. This **must** be manually called. If
1244+
the resource is left to be collected by the GC then the `destroy` hooks will
1245+
never be called.
1246+
1247+
### `eventemitterasyncresource.triggerAsyncId`
1248+
1249+
* Type: {number} The same `triggerAsyncId` that is passed to the
1250+
`AsyncResource` constructor.
1251+
11691252
<a id="event-target-and-event-api"></a>
11701253

11711254
## `EventTarget` and `Event` API
@@ -1706,7 +1789,9 @@ to the `EventTarget`.
17061789
[`events.defaultMaxListeners`]: #eventsdefaultmaxlisteners
17071790
[`fs.ReadStream`]: fs.md#class-fsreadstream
17081791
[`net.Server`]: net.md#class-netserver
1792+
[`new.target.name`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new.target
17091793
[`process.on('warning')`]: process.md#event-warning
1794+
[async context]: async_context.md
17101795
[capturerejections]: #capture-rejections-of-promises
17111796
[error]: #error-events
17121797
[rejection]: #emittersymbolfornodejsrejectionerr-eventname-args
Collapse file

‎lib/events.js‎

Copy file name to clipboardExpand all lines: lib/events.js
+130Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const {
2727
ArrayPrototypeShift,
2828
ArrayPrototypeSlice,
2929
ArrayPrototypeSplice,
30+
ArrayPrototypeUnshift,
3031
Boolean,
3132
Error,
3233
ErrorCaptureStackTrace,
@@ -42,6 +43,7 @@ const {
4243
Promise,
4344
PromiseReject,
4445
PromiseResolve,
46+
ReflectApply,
4547
ReflectOwnKeys,
4648
String,
4749
StringPrototypeSplit,
@@ -59,6 +61,7 @@ const {
5961
kEnhanceStackBeforeInspector,
6062
codes: {
6163
ERR_INVALID_ARG_TYPE,
64+
ERR_INVALID_THIS,
6265
ERR_OUT_OF_RANGE,
6366
ERR_UNHANDLED_ERROR
6467
},
@@ -68,6 +71,7 @@ const {
6871
validateAbortSignal,
6972
validateBoolean,
7073
validateFunction,
74+
validateString,
7175
} = require('internal/validators');
7276

7377
const kCapture = Symbol('kCapture');
@@ -76,6 +80,125 @@ const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
7680
const kMaxEventTargetListenersWarned =
7781
Symbol('events.maxEventTargetListenersWarned');
7882

83+
let EventEmitterAsyncResource;
84+
// The EventEmitterAsyncResource has to be initialized lazily because event.js
85+
// is loaded so early in the bootstrap process, before async_hooks is available.
86+
//
87+
// This implementation was adapted straight from addaleax's
88+
// eventemitter-asyncresource MIT-licensed userland module.
89+
// https://github.com/addaleax/eventemitter-asyncresource
90+
function lazyEventEmitterAsyncResource() {
91+
if (EventEmitterAsyncResource === undefined) {
92+
const {
93+
AsyncResource
94+
} = require('async_hooks');
95+
96+
const kEventEmitter = Symbol('kEventEmitter');
97+
const kAsyncResource = Symbol('kAsyncResource');
98+
class EventEmitterReferencingAsyncResource extends AsyncResource {
99+
/**
100+
* @param {EventEmitter} ee
101+
* @param {string} [type]
102+
* @param {{
103+
* triggerAsyncId?: number,
104+
* requireManualDestroy?: boolean,
105+
* }} [options]
106+
*/
107+
constructor(ee, type, options) {
108+
super(type, options);
109+
this[kEventEmitter] = ee;
110+
}
111+
112+
/**
113+
* @type {EventEmitter}
114+
*/
115+
get eventEmitter() {
116+
if (this[kEventEmitter] === undefined)
117+
throw new ERR_INVALID_THIS('EventEmitterReferencingAsyncResource');
118+
return this[kEventEmitter];
119+
}
120+
}
121+
122+
EventEmitterAsyncResource =
123+
class EventEmitterAsyncResource extends EventEmitter {
124+
/**
125+
* @param {{
126+
* name?: string,
127+
* triggerAsyncId?: number,
128+
* requireManualDestroy?: boolean,
129+
* }} [options]
130+
*/
131+
constructor(options = undefined) {
132+
let name;
133+
if (typeof options === 'string') {
134+
name = options;
135+
options = undefined;
136+
} else {
137+
if (new.target === EventEmitterAsyncResource) {
138+
validateString(options?.name, 'options.name');
139+
}
140+
name = options?.name || new.target.name;
141+
}
142+
super(options);
143+
144+
this[kAsyncResource] =
145+
new EventEmitterReferencingAsyncResource(this, name, options);
146+
}
147+
148+
/**
149+
* @param {symbol,string} event
150+
* @param {...any} args
151+
* @returns {boolean}
152+
*/
153+
emit(event, ...args) {
154+
if (this[kAsyncResource] === undefined)
155+
throw new ERR_INVALID_THIS('EventEmitterAsyncResource');
156+
const { asyncResource } = this;
157+
ArrayPrototypeUnshift(args, super.emit, this, event);
158+
return ReflectApply(asyncResource.runInAsyncScope, asyncResource,
159+
args);
160+
}
161+
162+
/**
163+
* @returns {void}
164+
*/
165+
emitDestroy() {
166+
if (this[kAsyncResource] === undefined)
167+
throw new ERR_INVALID_THIS('EventEmitterAsyncResource');
168+
this.asyncResource.emitDestroy();
169+
}
170+
171+
/**
172+
* @type {number}
173+
*/
174+
get asyncId() {
175+
if (this[kAsyncResource] === undefined)
176+
throw new ERR_INVALID_THIS('EventEmitterAsyncResource');
177+
return this.asyncResource.asyncId();
178+
}
179+
180+
/**
181+
* @type {number}
182+
*/
183+
get triggerAsyncId() {
184+
if (this[kAsyncResource] === undefined)
185+
throw new ERR_INVALID_THIS('EventEmitterAsyncResource');
186+
return this.asyncResource.triggerAsyncId();
187+
}
188+
189+
/**
190+
* @type {EventEmitterReferencingAsyncResource}
191+
*/
192+
get asyncResource() {
193+
if (this[kAsyncResource] === undefined)
194+
throw new ERR_INVALID_THIS('EventEmitterAsyncResource');
195+
return this[kAsyncResource];
196+
}
197+
};
198+
}
199+
return EventEmitterAsyncResource;
200+
}
201+
79202
/**
80203
* Creates a new `EventEmitter` instance.
81204
* @param {{ captureRejections?: boolean; }} [opts]
@@ -106,6 +229,13 @@ ObjectDefineProperty(EventEmitter, 'captureRejections', {
106229
enumerable: true
107230
});
108231

232+
ObjectDefineProperty(EventEmitter, 'EventEmitterAsyncResource', {
233+
enumerable: true,
234+
get: lazyEventEmitterAsyncResource,
235+
set: undefined,
236+
configurable: true,
237+
});
238+
109239
EventEmitter.errorMonitor = kErrorMonitor;
110240

111241
// The default for captureRejections is false

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.