1098 lines
32 KiB
JavaScript
1098 lines
32 KiB
JavaScript
import { AsyncResource } from "node:async_hooks";
|
|
// By default EventEmitters will print a warning if more than 10 listeners are
|
|
// added to it. This is a useful default which helps finding memory leaks.
|
|
let defaultMaxListeners = 10;
|
|
const AsyncIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf(async function* () {}).prototype);
|
|
// Inspect (mocked)
|
|
const inspect = (value, _opts) => value;
|
|
// Errors
|
|
const ERR_INVALID_THIS = Error;
|
|
const ERR_UNHANDLED_ERROR = Error;
|
|
const ERR_INVALID_ARG_TYPE = Error;
|
|
const AbortError = Error;
|
|
const genericNodeError = Error;
|
|
// Symbols
|
|
const kRejection = /* @__PURE__ */ Symbol.for("nodejs.rejection");
|
|
const kCapture = /* @__PURE__ */ Symbol.for("kCapture");
|
|
const kErrorMonitor = /* @__PURE__ */ Symbol.for("events.errorMonitor");
|
|
const kShapeMode = /* @__PURE__ */ Symbol.for("shapeMode");
|
|
const kMaxEventTargetListeners = /* @__PURE__ */ Symbol.for("events.maxEventTargetListeners");
|
|
const kEnhanceStackBeforeInspector = /* @__PURE__ */ Symbol.for("kEnhanceStackBeforeInspector");
|
|
const kWatermarkData = /* @__PURE__ */ Symbol.for("nodejs.watermarkData");
|
|
const kEventEmitter = /* @__PURE__ */ Symbol.for("kEventEmitter");
|
|
const kAsyncResource = /* @__PURE__ */ Symbol.for("kAsyncResource");
|
|
const kFirstEventParam = /* @__PURE__ */ Symbol.for("kFirstEventParam");
|
|
const kResistStopPropagation = /* @__PURE__ */ Symbol.for("kResistStopPropagation");
|
|
const kMaxEventTargetListenersWarned = /* @__PURE__ */ Symbol.for("events.maxEventTargetListenersWarned");
|
|
// ----------------------------------------------------------------------------
|
|
// EventEmitter
|
|
// ----------------------------------------------------------------------------
|
|
export class _EventEmitter {
|
|
// Internal state
|
|
_events = undefined;
|
|
_eventsCount = 0;
|
|
_maxListeners = defaultMaxListeners;
|
|
[kCapture] = false;
|
|
[kShapeMode] = false;
|
|
// Symbols
|
|
static captureRejectionSymbol = kRejection;
|
|
static errorMonitor = kErrorMonitor;
|
|
static kMaxEventTargetListeners = kMaxEventTargetListeners;
|
|
static kMaxEventTargetListenersWarned = kMaxEventTargetListenersWarned;
|
|
// Static utils
|
|
static usingDomains = false;
|
|
static get on() {
|
|
return on;
|
|
}
|
|
static get once() {
|
|
return once;
|
|
}
|
|
static get getEventListeners() {
|
|
return getEventListeners;
|
|
}
|
|
static get getMaxListeners() {
|
|
return getMaxListeners;
|
|
}
|
|
static get addAbortListener() {
|
|
return addAbortListener;
|
|
}
|
|
static get EventEmitterAsyncResource() {
|
|
return EventEmitterAsyncResource;
|
|
}
|
|
static get EventEmitter() {
|
|
return _EventEmitter;
|
|
}
|
|
static setMaxListeners(n = defaultMaxListeners, ...eventTargets) {
|
|
// validateNumber(n, "setMaxListeners", 0);
|
|
if (eventTargets.length === 0) {
|
|
defaultMaxListeners = n;
|
|
} else {
|
|
for (const target of eventTargets) {
|
|
if (isEventTarget(target)) {
|
|
// @ts-expect-error
|
|
target[kMaxEventTargetListeners] = n;
|
|
// @ts-expect-error
|
|
target[kMaxEventTargetListenersWarned] = false;
|
|
} else if (typeof target.setMaxListeners === "function") {
|
|
target.setMaxListeners(n);
|
|
} else {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
"eventTargets",
|
|
["EventEmitter", "EventTarget"],
|
|
// @ts-expect-error
|
|
target
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
static listenerCount(emitter, type) {
|
|
if (typeof emitter.listenerCount === "function") {
|
|
return emitter.listenerCount(type);
|
|
}
|
|
_EventEmitter.prototype.listenerCount.call(emitter, type);
|
|
}
|
|
static init() {
|
|
throw new Error("EventEmitter.init() is not implemented.");
|
|
}
|
|
static get captureRejections() {
|
|
// @ts-expect-error
|
|
return this[kCapture];
|
|
}
|
|
static set captureRejections(value) {
|
|
// validateBoolean(value, "captureRejections");
|
|
// @ts-expect-error
|
|
this[kCapture] = value;
|
|
}
|
|
static get defaultMaxListeners() {
|
|
return defaultMaxListeners;
|
|
}
|
|
static set defaultMaxListeners(arg) {
|
|
// validateNumber(arg, "defaultMaxListeners", 0);
|
|
defaultMaxListeners = arg;
|
|
}
|
|
// Constructor
|
|
constructor(opts) {
|
|
if (this._events === undefined || this._events === Object.getPrototypeOf(this)._events) {
|
|
this._events = { __proto__: null };
|
|
this._eventsCount = 0;
|
|
this[kShapeMode] = false;
|
|
} else {
|
|
this[kShapeMode] = true;
|
|
}
|
|
this._maxListeners = this._maxListeners || undefined;
|
|
if (opts?.captureRejections) {
|
|
// validateBoolean(opts.captureRejections, "options.captureRejections");
|
|
this[kCapture] = Boolean(opts.captureRejections);
|
|
} else {
|
|
// Assigning the kCapture property directly saves an expensive
|
|
// prototype lookup in a very sensitive hot path.
|
|
this[kCapture] = _EventEmitter.prototype[kCapture];
|
|
}
|
|
}
|
|
/**
|
|
* Increases the max listeners of the event emitter.
|
|
* @param {number} n
|
|
* @returns {EventEmitter}
|
|
*/
|
|
setMaxListeners(n) {
|
|
// validateNumber(n, "setMaxListeners", 0);
|
|
this._maxListeners = n;
|
|
return this;
|
|
}
|
|
/**
|
|
* Returns the current max listener value for the event emitter.
|
|
* @returns {number}
|
|
*/
|
|
getMaxListeners() {
|
|
return _getMaxListeners(this);
|
|
}
|
|
/**
|
|
* Synchronously calls each of the listeners registered
|
|
* for the event.
|
|
* @param {...any} [args]
|
|
* @returns {boolean}
|
|
*/
|
|
emit(type, ...args) {
|
|
let doError = type === "error";
|
|
const events = this._events;
|
|
if (events !== undefined) {
|
|
if (doError && events[kErrorMonitor] !== undefined) this.emit(kErrorMonitor, ...args);
|
|
doError = doError && events.error === undefined;
|
|
} else if (!doError) return false;
|
|
// If there is no 'error' event listener then throw.
|
|
if (doError) {
|
|
let er;
|
|
if (args.length > 0) er = args[0];
|
|
if (er instanceof Error) {
|
|
try {
|
|
const capture = {};
|
|
Error.captureStackTrace?.(capture, _EventEmitter.prototype.emit);
|
|
Object.defineProperty(er, kEnhanceStackBeforeInspector, {
|
|
__proto__: null,
|
|
value: Function.prototype.bind(enhanceStackTrace, this, er, capture),
|
|
configurable: true
|
|
});
|
|
} catch {}
|
|
// Note: The comments on the `throw` lines are intentional, they show
|
|
// up in Node's output if this results in an unhandled exception.
|
|
throw er;
|
|
}
|
|
let stringifiedEr;
|
|
try {
|
|
stringifiedEr = inspect(er);
|
|
} catch {
|
|
stringifiedEr = er;
|
|
}
|
|
// At least give some kind of context to the user
|
|
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
|
|
// @ts-expect-error
|
|
err.context = er;
|
|
throw err;
|
|
}
|
|
const handler = events[type];
|
|
if (handler === undefined) return false;
|
|
if (typeof handler === "function") {
|
|
const result = handler.apply(this, args);
|
|
// We check if result is undefined first because that
|
|
// is the most common case so we do not pay any perf
|
|
// penalty
|
|
if (result !== undefined && result !== null) {
|
|
addCatch(this, result, type, args);
|
|
}
|
|
} else {
|
|
const len = handler.length;
|
|
const listeners = arrayClone(handler);
|
|
for (let i = 0; i < len; ++i) {
|
|
const result = listeners[i].apply(this, args);
|
|
// We check if result is undefined first because that
|
|
// is the most common case so we do not pay any perf
|
|
// penalty.
|
|
// This code is duplicated because extracting it away
|
|
// would make it non-inlineable.
|
|
if (result !== undefined && result !== null) {
|
|
addCatch(this, result, type, args);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
/**
|
|
* Adds a listener to the event emitter.
|
|
* @returns {EventEmitter}
|
|
*/
|
|
addListener(type, listener) {
|
|
_addListener(this, type, listener, false);
|
|
return this;
|
|
}
|
|
on(type, listener) {
|
|
return this.addListener(type, listener);
|
|
}
|
|
/**
|
|
* Adds the `listener` function to the beginning of
|
|
* the listeners array.
|
|
*/
|
|
prependListener(type, listener) {
|
|
_addListener(this, type, listener, true);
|
|
return this;
|
|
}
|
|
/**
|
|
* Adds a one-time `listener` function to the event emitter.
|
|
*/
|
|
once(type, listener) {
|
|
checkListener(listener);
|
|
this.on(type, _onceWrap(this, type, listener));
|
|
return this;
|
|
}
|
|
/**
|
|
* Adds a one-time `listener` function to the beginning of
|
|
* the listeners array.
|
|
*/
|
|
prependOnceListener(type, listener) {
|
|
checkListener(listener);
|
|
this.prependListener(type, _onceWrap(this, type, listener));
|
|
return this;
|
|
}
|
|
/**
|
|
* Removes the specified `listener` from the listeners array.
|
|
* @param {string | symbol} type
|
|
* @param {Function} listener
|
|
* @returns {EventEmitter}
|
|
*/
|
|
removeListener(type, listener) {
|
|
checkListener(listener);
|
|
const events = this._events;
|
|
if (events === undefined) return this;
|
|
const list = events[type];
|
|
if (list === undefined) return this;
|
|
if (list === listener || list.listener === listener) {
|
|
this._eventsCount -= 1;
|
|
if (this[kShapeMode]) {
|
|
events[type] = undefined;
|
|
} else if (this._eventsCount === 0) {
|
|
this._events = { __proto__: null };
|
|
} else {
|
|
delete events[type];
|
|
if (events.removeListener) this.emit("removeListener", type, list.listener || listener);
|
|
}
|
|
} else if (typeof list !== "function") {
|
|
let position = -1;
|
|
for (let i = list.length - 1; i >= 0; i--) {
|
|
if (list[i] === listener || list[i].listener === listener) {
|
|
position = i;
|
|
break;
|
|
}
|
|
}
|
|
if (position < 0) return this;
|
|
if (position === 0) list.shift();
|
|
else {
|
|
spliceOne(list, position);
|
|
}
|
|
if (list.length === 1) events[type] = list[0];
|
|
if (events.removeListener !== undefined) this.emit("removeListener", type, listener);
|
|
}
|
|
return this;
|
|
}
|
|
off(type, listener) {
|
|
return this.removeListener(type, listener);
|
|
}
|
|
/**
|
|
* Removes all listeners from the event emitter. (Only
|
|
* removes listeners for a specific event name if specified
|
|
* as `type`).
|
|
*/
|
|
removeAllListeners(type) {
|
|
const events = this._events;
|
|
if (events === undefined) return this;
|
|
// Not listening for removeListener, no need to emit
|
|
if (events.removeListener === undefined) {
|
|
if (arguments.length === 0) {
|
|
this._events = { __proto__: null };
|
|
this._eventsCount = 0;
|
|
} else if (events[type] !== undefined) {
|
|
if (--this._eventsCount === 0) this._events = { __proto__: null };
|
|
else delete events[type];
|
|
}
|
|
this[kShapeMode] = false;
|
|
return this;
|
|
}
|
|
// Emit removeListener for all listeners on all events
|
|
if (arguments.length === 0) {
|
|
for (const key of Reflect.ownKeys(events)) {
|
|
if (key === "removeListener") continue;
|
|
this.removeAllListeners(key);
|
|
}
|
|
this.removeAllListeners("removeListener");
|
|
this._events = { __proto__: null };
|
|
this._eventsCount = 0;
|
|
this[kShapeMode] = false;
|
|
return this;
|
|
}
|
|
const listeners = events[type];
|
|
if (typeof listeners === "function") {
|
|
this.removeListener(type, listeners);
|
|
} else if (listeners !== undefined) {
|
|
// LIFO order
|
|
for (let i = listeners.length - 1; i >= 0; i--) {
|
|
this.removeListener(type, listeners[i]);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
/**
|
|
* Returns a copy of the array of listeners for the event name
|
|
* specified as `type`.
|
|
* @param {string | symbol} type
|
|
* @returns {Function[]}
|
|
*/
|
|
listeners(type) {
|
|
return _listeners(this, type, true);
|
|
}
|
|
/**
|
|
* Returns a copy of the array of listeners and wrappers for
|
|
* the event name specified as `type`.
|
|
* @returns {Function[]}
|
|
*/
|
|
rawListeners(type) {
|
|
return _listeners(this, type, false);
|
|
}
|
|
/**
|
|
* Returns an array listing the events for which
|
|
* the emitter has registered listeners.
|
|
* @returns {any[]}
|
|
*/
|
|
eventNames() {
|
|
return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
|
|
}
|
|
/**
|
|
* Returns the number of listeners listening to event name
|
|
*/
|
|
listenerCount(eventName, listener) {
|
|
const events = this._events;
|
|
if (events !== undefined) {
|
|
const evlistener = events[eventName];
|
|
if (typeof evlistener === "function") {
|
|
if (listener != null) {
|
|
return listener === evlistener || listener === evlistener.listener ? 1 : 0;
|
|
}
|
|
return 1;
|
|
} else if (evlistener !== undefined) {
|
|
if (listener != null) {
|
|
let matching = 0;
|
|
for (let i = 0, l = evlistener.length; i < l; i++) {
|
|
if (evlistener[i] === listener || evlistener[i].listener === listener) {
|
|
matching++;
|
|
}
|
|
}
|
|
return matching;
|
|
}
|
|
return evlistener.length;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
// EventEmitterAsyncResource
|
|
// ----------------------------------------------------------------------------
|
|
export class EventEmitterAsyncResource extends _EventEmitter {
|
|
/**
|
|
* @param {{
|
|
* name?: string,
|
|
* triggerAsyncId?: number,
|
|
* requireManualDestroy?: boolean,
|
|
* }} [options]
|
|
*/
|
|
constructor(options) {
|
|
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);
|
|
// @ts-expect-error
|
|
this[kAsyncResource] = new EventEmitterReferencingAsyncResource(this, name, options);
|
|
}
|
|
/**
|
|
* @param {symbol,string} event
|
|
* @param {...any} args
|
|
* @returns {boolean}
|
|
*/
|
|
emit(event, ...args) {
|
|
// @ts-expect-error
|
|
if (this[kAsyncResource] === undefined) throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
|
|
const { asyncResource } = this;
|
|
Array.prototype.unshift(args, super.emit, this, event);
|
|
return Reflect.apply(asyncResource.runInAsyncScope, asyncResource, args);
|
|
}
|
|
/**
|
|
* @returns {void}
|
|
*/
|
|
emitDestroy() {
|
|
// @ts-expect-error
|
|
if (this[kAsyncResource] === undefined) throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
|
|
this.asyncResource.emitDestroy();
|
|
}
|
|
/**
|
|
* @type {number}
|
|
*/
|
|
get asyncId() {
|
|
// @ts-expect-error
|
|
if (this[kAsyncResource] === undefined) throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
|
|
return this.asyncResource.asyncId();
|
|
}
|
|
/**
|
|
* @type {number}
|
|
*/
|
|
get triggerAsyncId() {
|
|
// @ts-expect-error
|
|
if (this[kAsyncResource] === undefined) throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
|
|
return this.asyncResource.triggerAsyncId();
|
|
}
|
|
/**
|
|
* @type {EventEmitterReferencingAsyncResource}
|
|
*/
|
|
get asyncResource() {
|
|
// @ts-expect-error
|
|
if (this[kAsyncResource] === undefined) throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
|
|
// @ts-expect-error
|
|
return this[kAsyncResource];
|
|
}
|
|
}
|
|
// This implementation was adapted straight from addaleax's
|
|
// eventemitter-asyncresource MIT-licensed userland module.
|
|
// https://github.com/addaleax/eventemitter-asyncresource
|
|
class EventEmitterReferencingAsyncResource extends AsyncResource {
|
|
/**
|
|
* @param {EventEmitter} ee
|
|
* @param {string} [type]
|
|
* @param {{
|
|
* triggerAsyncId?: number,
|
|
* requireManualDestroy?: boolean,
|
|
* }} [options]
|
|
*/
|
|
constructor(ee, type, options) {
|
|
super(type, options);
|
|
// @ts-expect-error
|
|
this[kEventEmitter] = ee;
|
|
}
|
|
/**
|
|
* @type {EventEmitter}
|
|
*/
|
|
get eventEmitter() {
|
|
// @ts-expect-error
|
|
if (this[kEventEmitter] === undefined) throw new ERR_INVALID_THIS("EventEmitterReferencingAsyncResource");
|
|
// @ts-expect-error
|
|
return this[kEventEmitter];
|
|
}
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
// Exported utils
|
|
// ----------------------------------------------------------------------------
|
|
/**
|
|
* Returns an `AsyncIterator` that iterates `event` events.
|
|
* @param {EventEmitter} emitter
|
|
* @param {string | symbol} event
|
|
* @param {{
|
|
* signal: AbortSignal;
|
|
* close?: string[];
|
|
* highWaterMark?: number,
|
|
* lowWaterMark?: number
|
|
* }} [options]
|
|
* @returns {AsyncIterator}
|
|
*/
|
|
export const on = function on(emitter, event, options = {}) {
|
|
// Parameters validation
|
|
// validateObject(options, "options");
|
|
const signal = options.signal;
|
|
// validateAbortSignal(signal, "options.signal");
|
|
if (signal?.aborted) {
|
|
throw new AbortError(undefined, { cause: signal?.reason });
|
|
}
|
|
// Support both highWaterMark and highWatermark for backward compatibility
|
|
const highWatermark = options.highWaterMark ?? options.highWatermark ?? Number.MAX_SAFE_INTEGER;
|
|
// validateInteger(highWatermark, "options.highWaterMark", 1);
|
|
// Support both lowWaterMark and lowWatermark for backward compatibility
|
|
const lowWatermark = options.lowWaterMark ?? options.lowWatermark ?? 1;
|
|
// validateInteger(lowWatermark, "options.lowWaterMark", 1);
|
|
// Preparing controlling queues and variables
|
|
const unconsumedEvents = new FixedQueue();
|
|
const unconsumedPromises = new FixedQueue();
|
|
let paused = false;
|
|
let error = null;
|
|
let finished = false;
|
|
let size = 0;
|
|
const iterator = Object.setPrototypeOf({
|
|
next() {
|
|
// First, we consume all unread events
|
|
if (size) {
|
|
const value = unconsumedEvents.shift();
|
|
size--;
|
|
if (paused && size < lowWatermark) {
|
|
// @ts-expect-error
|
|
emitter.resume?.();
|
|
paused = false;
|
|
}
|
|
return Promise.resolve(createIterResult(value, false));
|
|
}
|
|
// Then we error, if an error happened
|
|
// This happens one time if at all, because after 'error'
|
|
// we stop listening
|
|
if (error) {
|
|
const p = Promise.reject(error);
|
|
// Only the first element errors
|
|
error = null;
|
|
return p;
|
|
}
|
|
// If the iterator is finished, resolve to done
|
|
if (finished) return closeHandler();
|
|
// Wait until an event happens
|
|
return new Promise(function(resolve, reject) {
|
|
unconsumedPromises.push({
|
|
resolve,
|
|
reject
|
|
});
|
|
});
|
|
},
|
|
return() {
|
|
return closeHandler();
|
|
},
|
|
throw(err) {
|
|
if (!err || !(err instanceof Error)) {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
"EventEmitter.AsyncIterator",
|
|
"Error",
|
|
// @ts-expect-error
|
|
err
|
|
);
|
|
}
|
|
errorHandler(err);
|
|
},
|
|
[Symbol.asyncIterator]() {
|
|
return this;
|
|
},
|
|
[kWatermarkData]: {
|
|
get size() {
|
|
return size;
|
|
},
|
|
get low() {
|
|
return lowWatermark;
|
|
},
|
|
get high() {
|
|
return highWatermark;
|
|
},
|
|
get isPaused() {
|
|
return paused;
|
|
}
|
|
}
|
|
}, AsyncIteratorPrototype);
|
|
// Adding event handlers
|
|
const { addEventListener, removeAll } = listenersController();
|
|
addEventListener(emitter, event, options[kFirstEventParam] ? eventHandler : function(...args) {
|
|
return eventHandler(args);
|
|
});
|
|
if (event !== "error" && typeof emitter.on === "function") {
|
|
addEventListener(emitter, "error", errorHandler);
|
|
}
|
|
const closeEvents = options?.close;
|
|
if (closeEvents?.length) {
|
|
for (const closeEvent of closeEvents) {
|
|
addEventListener(emitter, closeEvent, closeHandler);
|
|
}
|
|
}
|
|
const abortListenerDisposable = signal ? addAbortListener(signal, abortListener) : null;
|
|
return iterator;
|
|
function abortListener() {
|
|
errorHandler(new AbortError(undefined, { cause: signal?.reason }));
|
|
}
|
|
function eventHandler(value) {
|
|
if (unconsumedPromises.isEmpty()) {
|
|
size++;
|
|
if (!paused && size > highWatermark) {
|
|
paused = true;
|
|
// @ts-expect-error
|
|
emitter.pause?.();
|
|
}
|
|
unconsumedEvents.push(value);
|
|
} else unconsumedPromises.shift().resolve(createIterResult(value, false));
|
|
}
|
|
function errorHandler(err) {
|
|
if (unconsumedPromises.isEmpty()) error = err;
|
|
else unconsumedPromises.shift().reject(err);
|
|
closeHandler();
|
|
}
|
|
function closeHandler() {
|
|
abortListenerDisposable?.[Symbol.dispose]();
|
|
removeAll();
|
|
finished = true;
|
|
const doneResult = createIterResult(undefined, true);
|
|
while (!unconsumedPromises.isEmpty()) {
|
|
unconsumedPromises.shift().resolve(doneResult);
|
|
}
|
|
return Promise.resolve(doneResult);
|
|
}
|
|
};
|
|
/**
|
|
* Creates a `Promise` that is fulfilled when the emitter
|
|
* emits the given event.
|
|
* @param {EventEmitter} emitter
|
|
* @param {string} name
|
|
* @param {{ signal: AbortSignal; }} [options]
|
|
* @returns {Promise}
|
|
*/
|
|
export const once = async function once(emitter, name, options = {}) {
|
|
// validateObject(options, "options");
|
|
const signal = options?.signal;
|
|
// validateAbortSignal(signal, "options.signal");
|
|
if (signal?.aborted) {
|
|
throw new AbortError(undefined, { cause: signal?.reason });
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
const errorListener = (err) => {
|
|
if (typeof emitter.removeListener === "function") {
|
|
emitter.removeListener(name, resolver);
|
|
}
|
|
if (signal != null) {
|
|
eventTargetAgnosticRemoveListener(signal, "abort", abortListener);
|
|
}
|
|
reject(err);
|
|
};
|
|
const resolver = (...args) => {
|
|
if (typeof emitter.removeListener === "function") {
|
|
emitter.removeListener("error", errorListener);
|
|
}
|
|
if (signal != null) {
|
|
eventTargetAgnosticRemoveListener(signal, "abort", abortListener);
|
|
}
|
|
resolve(args);
|
|
};
|
|
const opts = {
|
|
__proto__: null,
|
|
once: true,
|
|
[kResistStopPropagation]: true
|
|
};
|
|
eventTargetAgnosticAddListener(emitter, name, resolver, opts);
|
|
if (name !== "error" && typeof emitter.once === "function") {
|
|
// EventTarget does not have `error` event semantics like Node
|
|
// EventEmitters, we listen to `error` events only on EventEmitters.
|
|
emitter.once("error", errorListener);
|
|
}
|
|
function abortListener() {
|
|
eventTargetAgnosticRemoveListener(emitter, name, resolver);
|
|
eventTargetAgnosticRemoveListener(emitter, "error", errorListener);
|
|
reject(new AbortError(undefined, { cause: signal?.reason }));
|
|
}
|
|
if (signal != null) {
|
|
eventTargetAgnosticAddListener(signal, "abort", abortListener, {
|
|
__proto__: null,
|
|
once: true,
|
|
[kResistStopPropagation]: true
|
|
});
|
|
}
|
|
});
|
|
};
|
|
export const addAbortListener = function addAbortListener(signal, listener) {
|
|
if (signal === undefined) {
|
|
// @ts-expect-error
|
|
throw new ERR_INVALID_ARG_TYPE("signal", "AbortSignal", signal);
|
|
}
|
|
// validateAbortSignal(signal, 'signal');
|
|
// validateFunction(listener, 'listener');
|
|
let removeEventListener;
|
|
if (signal.aborted) {
|
|
// @ts-expect-error
|
|
queueMicrotask(() => listener());
|
|
} else {
|
|
// TODO(atlowChemi) add { subscription: true } and return directly
|
|
signal.addEventListener("abort", listener, {
|
|
__proto__: null,
|
|
once: true,
|
|
[kResistStopPropagation]: true
|
|
});
|
|
removeEventListener = () => {
|
|
signal.removeEventListener("abort", listener);
|
|
};
|
|
}
|
|
return {
|
|
__proto__: null,
|
|
[Symbol.dispose]() {
|
|
removeEventListener?.();
|
|
}
|
|
};
|
|
};
|
|
/**
|
|
* Returns a copy of the array of listeners for the event name
|
|
* specified as `type`.
|
|
* @returns {Function[]}
|
|
*/
|
|
export const getEventListeners = function getEventListeners(emitterOrTarget, type) {
|
|
// First check if EventEmitter
|
|
if (typeof emitterOrTarget.listeners === "function") {
|
|
return emitterOrTarget.listeners(type);
|
|
}
|
|
// Require event target lazily to avoid always loading it
|
|
if (isEventTarget(emitterOrTarget)) {
|
|
// @ts-expect-error
|
|
const root = emitterOrTarget[kEvents].get(type);
|
|
const listeners = [];
|
|
let handler = root?.next;
|
|
while (handler?.listener !== undefined) {
|
|
const listener = handler.listener?.deref ? handler.listener.deref() : handler.listener;
|
|
listeners.push(listener);
|
|
handler = handler.next;
|
|
}
|
|
return listeners;
|
|
}
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
"emitter",
|
|
["EventEmitter", "EventTarget"],
|
|
// @ts-expect-error
|
|
emitterOrTarget
|
|
);
|
|
};
|
|
/**
|
|
* Returns the max listeners set.
|
|
* @param {EventEmitter | EventTarget} emitterOrTarget
|
|
* @returns {number}
|
|
*/
|
|
export const getMaxListeners = function getMaxListeners(emitterOrTarget) {
|
|
if (typeof emitterOrTarget?.getMaxListeners === "function") {
|
|
return _getMaxListeners(emitterOrTarget);
|
|
} else if (emitterOrTarget?.[kMaxEventTargetListeners]) {
|
|
// @ts-expect-error
|
|
return emitterOrTarget[kMaxEventTargetListeners];
|
|
}
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
"emitter",
|
|
["EventEmitter", "EventTarget"],
|
|
// @ts-expect-error
|
|
emitterOrTarget
|
|
);
|
|
};
|
|
// ----------------------------------------------------------------------------
|
|
// FixedQueue (internal)
|
|
// ----------------------------------------------------------------------------
|
|
// Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two.
|
|
const kSize = 2048;
|
|
const kMask = kSize - 1;
|
|
class FixedCircularBuffer {
|
|
bottom;
|
|
top;
|
|
list;
|
|
next;
|
|
constructor() {
|
|
this.bottom = 0;
|
|
this.top = 0;
|
|
this.list = new Array(kSize);
|
|
this.next = null;
|
|
}
|
|
isEmpty() {
|
|
return this.top === this.bottom;
|
|
}
|
|
isFull() {
|
|
return (this.top + 1 & kMask) === this.bottom;
|
|
}
|
|
push(data) {
|
|
this.list[this.top] = data;
|
|
this.top = this.top + 1 & kMask;
|
|
}
|
|
shift() {
|
|
const nextItem = this.list[this.bottom];
|
|
if (nextItem === undefined) return null;
|
|
this.list[this.bottom] = undefined;
|
|
this.bottom = this.bottom + 1 & kMask;
|
|
return nextItem;
|
|
}
|
|
}
|
|
class FixedQueue {
|
|
head;
|
|
tail;
|
|
constructor() {
|
|
this.head = this.tail = new FixedCircularBuffer();
|
|
}
|
|
isEmpty() {
|
|
return this.head.isEmpty();
|
|
}
|
|
push(data) {
|
|
if (this.head.isFull()) {
|
|
// Head is full: Creates a new queue, sets the old queue's `.next` to it,
|
|
// and sets it as the new main queue.
|
|
this.head = this.head.next = new FixedCircularBuffer();
|
|
}
|
|
this.head.push(data);
|
|
}
|
|
shift() {
|
|
const tail = this.tail;
|
|
const next = tail.shift();
|
|
if (tail.isEmpty() && tail.next !== null) {
|
|
// If there is another queue, it forms the new tail.
|
|
this.tail = tail.next;
|
|
tail.next = null;
|
|
}
|
|
return next;
|
|
}
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
// Internal utils
|
|
// ----------------------------------------------------------------------------
|
|
function isEventTarget(emitter) {
|
|
return typeof emitter?.addEventListener === "function";
|
|
}
|
|
function checkListener(listener) {
|
|
// validateFunction(listener, "listener");
|
|
}
|
|
function addCatch(that, promise, type, args) {
|
|
if (!that[kCapture]) {
|
|
return;
|
|
}
|
|
// Handle Promises/A+ spec, then could be a getter
|
|
// that throws on second use.
|
|
try {
|
|
const then = promise.then;
|
|
if (typeof then === "function") {
|
|
then.call(promise, undefined, function(err) {
|
|
// The callback is called with nextTick to avoid a follow-up
|
|
// rejection from this promise.
|
|
// Avoid using process. from events to avoid circular dependency
|
|
// process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args);
|
|
setTimeout(emitUnhandledRejectionOrErr, 0, that, err, type, args);
|
|
});
|
|
}
|
|
} catch (error_) {
|
|
that.emit("error", error_);
|
|
}
|
|
}
|
|
function emitUnhandledRejectionOrErr(ee, err, type, args) {
|
|
// @ts-expect-error
|
|
if (typeof ee[kRejection] === "function") {
|
|
// @ts-expect-error
|
|
ee[kRejection](err, type, ...args);
|
|
} else {
|
|
// We have to disable the capture rejections mechanism, otherwise
|
|
// we might end up in an infinite loop.
|
|
const prev = ee[kCapture];
|
|
// If the error handler throws, it is not catchable and it
|
|
// will end up in 'uncaughtException'. We restore the previous
|
|
// value of kCapture in case the uncaughtException is present
|
|
// and the exception is handled.
|
|
try {
|
|
ee[kCapture] = false;
|
|
ee.emit("error", err);
|
|
} finally {
|
|
ee[kCapture] = prev;
|
|
}
|
|
}
|
|
}
|
|
function _getMaxListeners(that) {
|
|
if (that._maxListeners === undefined) return defaultMaxListeners;
|
|
return that._maxListeners;
|
|
}
|
|
function enhanceStackTrace(err, own) {
|
|
let ctorInfo = "";
|
|
try {
|
|
// @ts-expect-error
|
|
const { name } = this.constructor;
|
|
if (name !== "EventEmitter") ctorInfo = ` on ${name} instance`;
|
|
} catch {}
|
|
const sep = `\nEmitted 'error' event${ctorInfo} at:\n`;
|
|
// const errStack = (err.stack || "").split("\n").slice(1);
|
|
const ownStack = (own.stack || "").split("\n").slice(1);
|
|
// const { len, offset } = identicalSequenceRange(ownStack, errStack);
|
|
// if (len > 0) {
|
|
// Array.prototype.splice(
|
|
// ownStack,
|
|
// offset + 1,
|
|
// len - 2,
|
|
// " [... lines matching original stack trace ...]",
|
|
// );
|
|
// }
|
|
return err.stack + sep + ownStack.join("\n");
|
|
}
|
|
function _addListener(target, type, listener, prepend) {
|
|
let m;
|
|
let events;
|
|
let existing;
|
|
checkListener(listener);
|
|
events = target._events;
|
|
if (events === undefined) {
|
|
events = target._events = { __proto__: null };
|
|
target._eventsCount = 0;
|
|
} else {
|
|
// To avoid recursion in the case that type === "newListener"! Before
|
|
// adding it to the listeners, first emit "newListener".
|
|
if (events.newListener !== undefined) {
|
|
// @ts-expect-error
|
|
target.emit("newListener", type, listener.listener ?? listener);
|
|
// Re-assign `events` because a newListener handler could have caused the
|
|
// this._events to be assigned to a new object
|
|
events = target._events;
|
|
}
|
|
existing = events[type];
|
|
}
|
|
if (existing === undefined) {
|
|
// Optimize the case of one listener. Don't need the extra array object.
|
|
events[type] = listener;
|
|
++target._eventsCount;
|
|
} else {
|
|
if (typeof existing === "function") {
|
|
// Adding the second element, need to change to array.
|
|
existing = events[type] = prepend ? [listener, existing] : [existing, listener];
|
|
} else if (prepend) {
|
|
existing.unshift(listener);
|
|
} else {
|
|
existing.push(listener);
|
|
}
|
|
// Check for listener leak
|
|
m = _getMaxListeners(target);
|
|
if (m > 0 && existing.length > m && !existing.warned) {
|
|
existing.warned = true;
|
|
// No error code for this since it is a Warning
|
|
const w = new genericNodeError(`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` + `added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`, {
|
|
name: "MaxListenersExceededWarning",
|
|
emitter: target,
|
|
type,
|
|
count: existing.length
|
|
});
|
|
// Avoid using process from events to avoid circular dependency
|
|
console.warn(w);
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
function onceWrapper() {
|
|
// @ts-expect-error
|
|
if (!this.fired) {
|
|
// @ts-expect-error
|
|
this.target.removeListener(this.type, this.wrapFn);
|
|
// @ts-expect-error
|
|
this.fired = true;
|
|
// @ts-expect-error
|
|
if (arguments.length === 0) return this.listener.call(this.target);
|
|
// @ts-expect-error
|
|
return this.listener.apply(this.target, arguments);
|
|
}
|
|
}
|
|
function _onceWrap(target, type, listener) {
|
|
const state = {
|
|
fired: false,
|
|
wrapFn: undefined,
|
|
target,
|
|
type,
|
|
listener
|
|
};
|
|
const wrapped = onceWrapper.bind(state);
|
|
wrapped.listener = listener;
|
|
state.wrapFn = wrapped;
|
|
return wrapped;
|
|
}
|
|
function _listeners(target, type, unwrap) {
|
|
const events = target._events;
|
|
if (events === undefined) return [];
|
|
const evlistener = events[type];
|
|
if (evlistener === undefined) return [];
|
|
if (typeof evlistener === "function") return unwrap ? [evlistener.listener || evlistener] : [evlistener];
|
|
return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener);
|
|
}
|
|
function arrayClone(arr) {
|
|
// At least since V8 8.3, this implementation is faster than the previous
|
|
// which always used a simple for-loop
|
|
switch (arr.length) {
|
|
case 2: return [arr[0], arr[1]];
|
|
case 3: return [
|
|
arr[0],
|
|
arr[1],
|
|
arr[2]
|
|
];
|
|
case 4: return [
|
|
arr[0],
|
|
arr[1],
|
|
arr[2],
|
|
arr[3]
|
|
];
|
|
case 5: return [
|
|
arr[0],
|
|
arr[1],
|
|
arr[2],
|
|
arr[3],
|
|
arr[4]
|
|
];
|
|
case 6: return [
|
|
arr[0],
|
|
arr[1],
|
|
arr[2],
|
|
arr[3],
|
|
arr[4],
|
|
arr[5]
|
|
];
|
|
}
|
|
return Array.prototype.slice.call(arr);
|
|
}
|
|
function unwrapListeners(arr) {
|
|
const ret = arrayClone(arr);
|
|
for (let i = 0; i < ret.length; ++i) {
|
|
const orig = ret[i].listener;
|
|
if (typeof orig === "function") ret[i] = orig;
|
|
}
|
|
return ret;
|
|
}
|
|
function createIterResult(value, done) {
|
|
return {
|
|
value,
|
|
done
|
|
};
|
|
}
|
|
function eventTargetAgnosticRemoveListener(emitter, name, listener, flags) {
|
|
if (typeof emitter.removeListener === "function") {
|
|
emitter.removeListener(name, listener);
|
|
} else if (typeof emitter.removeEventListener === "function") {
|
|
emitter.removeEventListener(name, listener, flags);
|
|
} else {
|
|
// @ts-expect-error
|
|
throw new ERR_INVALID_ARG_TYPE("emitter", "EventEmitter", emitter);
|
|
}
|
|
}
|
|
function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
|
|
if (typeof emitter.on === "function") {
|
|
if (flags?.once) {
|
|
emitter.once(name, listener);
|
|
} else {
|
|
emitter.on(name, listener);
|
|
}
|
|
} else if (typeof emitter.addEventListener === "function") {
|
|
emitter.addEventListener(name, listener, flags);
|
|
} else {
|
|
// @ts-expect-error
|
|
throw new ERR_INVALID_ARG_TYPE("emitter", "EventEmitter", emitter);
|
|
}
|
|
}
|
|
function listenersController() {
|
|
const listeners = [];
|
|
return {
|
|
addEventListener(emitter, event, handler, flags) {
|
|
eventTargetAgnosticAddListener(emitter, event, handler, flags);
|
|
Array.prototype.push(listeners, [
|
|
emitter,
|
|
event,
|
|
handler,
|
|
flags
|
|
]);
|
|
},
|
|
removeAll() {
|
|
while (listeners.length > 0) {
|
|
Reflect.apply(eventTargetAgnosticRemoveListener, undefined, listeners.pop());
|
|
}
|
|
}
|
|
};
|
|
}
|
|
// As of V8 6.6, depending on the size of the array, this is anywhere
|
|
// between 1.5-10x faster than the two-arg version of Array#splice()
|
|
function spliceOne(list, index) {
|
|
for (; index + 1 < list.length; index++) list[index] = list[index + 1];
|
|
list.pop();
|
|
}
|