fix(ext/node): implement EventEmitterAsyncResource (#22994)

Fixes #22729
This commit is contained in:
Divy Srivastava 2024-03-20 11:20:18 +05:30 committed by GitHub
parent 5b2f689f08
commit 724cdcec7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 127 additions and 1 deletions

View file

@ -32,14 +32,17 @@ import {
AbortError,
// kEnhanceStackBeforeInspector,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_THIS,
ERR_OUT_OF_RANGE,
ERR_UNHANDLED_ERROR,
} from "ext:deno_node/internal/errors.ts";
import { AsyncResource } from "node:async_hooks";
import {
validateAbortSignal,
validateBoolean,
validateFunction,
validateString,
} from "ext:deno_node/internal/validators.mjs";
import { spliceOne } from "ext:deno_node/_utils.ts";
import { nextTick } from "ext:deno_node/_process/process.ts";
@ -1035,3 +1038,116 @@ export function on(emitter, event, options) {
iterator.return();
}
}
const kAsyncResource = Symbol("kAsyncResource");
const kEventEmitter = Symbol("kEventEmitter");
class EventEmitterReferencingAsyncResource extends AsyncResource {
/**
* @param {EventEmitter} ee
* @param {string} [type]
* @param {{
* triggerAsyncId?: number,
* requireManualDestroy?: boolean,
* }} [options]
*/
constructor(ee, type, options) {
super(type, options);
this[kEventEmitter] = ee;
}
/**
* @type {EventEmitter}
*/
get eventEmitter() {
if (this[kEventEmitter] === undefined) {
throw new ERR_INVALID_THIS("EventEmitterReferencingAsyncResource");
}
return this[kEventEmitter];
}
}
export class EventEmitterAsyncResource extends EventEmitter {
/**
* @param {{
* name?: string,
* triggerAsyncId?: number,
* requireManualDestroy?: boolean,
* }} [options]
*/
constructor(options = undefined) {
let name;
if (typeof options === "string") {
name = options;
options = undefined;
} else {
if (new.target === EventEmitterAsyncResource) {
validateString(options?.name, "options.name");
}
name = options?.name || new.target.name;
}
super(options);
this[kAsyncResource] = new EventEmitterReferencingAsyncResource(
this,
name,
options,
);
}
/**
* @param {symbol,string} event
* @param {...any} args
* @returns {boolean}
*/
emit(event, ...args) {
if (this[kAsyncResource] === undefined) {
throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
}
const { asyncResource } = this;
args.unshift(super.emit, this, event);
return asyncResource.runInAsyncScope.apply(asyncResource, args);
}
/**
* @returns {void}
*/
emitDestroy() {
if (this[kAsyncResource] === undefined) {
throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
}
this.asyncResource.emitDestroy();
}
/**
* @type {number}
*/
get asyncId() {
if (this[kAsyncResource] === undefined) {
throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
}
return this.asyncResource.asyncId();
}
/**
* @type {number}
*/
get triggerAsyncId() {
if (this[kAsyncResource] === undefined) {
throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
}
return this.asyncResource.triggerAsyncId();
}
/**
* @type {EventEmitterReferencingAsyncResource}
*/
get asyncResource() {
if (this[kAsyncResource] === undefined) {
throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
}
return this[kAsyncResource];
}
}
EventEmitter.EventEmitterAsyncResource = EventEmitterAsyncResource;

View file

@ -6,6 +6,7 @@ export {
defaultMaxListeners,
errorMonitor,
EventEmitter,
EventEmitterAsyncResource,
getEventListeners,
listenerCount,
on,

View file

@ -1,6 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { EventEmitter } from "node:events";
import events, { EventEmitter } from "node:events";
EventEmitter.captureRejections = true;
@ -25,3 +25,12 @@ Deno.test("regression #20441", async () => {
ee.emit("foo");
await promise;
});
Deno.test("eventemitter async resource", () => {
// @ts-ignore: @types/node is outdated
class Foo extends events.EventEmitterAsyncResource {}
const foo = new Foo();
// @ts-ignore: @types/node is outdated
foo.emit("bar");
});