mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Revert "implement node:events in javascript (#2604)"
This reverts commit 96a2ed1040.
This commit is contained in:
BIN
bench/bun.lockb
BIN
bench/bun.lockb
Binary file not shown.
@@ -1,31 +0,0 @@
|
||||
import EventEmitter3 from "eventemitter3";
|
||||
import { group } from "mitata";
|
||||
import EventEmitterNative from "node:events";
|
||||
|
||||
export const implementations = [
|
||||
{
|
||||
EventEmitter: EventEmitterNative,
|
||||
name: process.isBun ? (EventEmitterNative.init ? "bun" : "C++") : "node:events",
|
||||
monkey: true,
|
||||
},
|
||||
// { EventEmitter: EventEmitter3, name: "EventEmitter3" },
|
||||
].filter(Boolean);
|
||||
|
||||
for (const impl of implementations) {
|
||||
impl.EventEmitter?.setMaxListeners?.(Infinity);
|
||||
}
|
||||
|
||||
export function groupForEmitter(name, cb) {
|
||||
if (implementations.length === 1) {
|
||||
return cb({
|
||||
...implementations[0],
|
||||
name: `${name}: ${implementations[0].name}`,
|
||||
});
|
||||
} else {
|
||||
return group(name, () => {
|
||||
for (let impl of implementations) {
|
||||
cb(impl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import { bench, run } from "mitata";
|
||||
import { groupForEmitter } from "./implementations.mjs";
|
||||
|
||||
var id = 0;
|
||||
|
||||
groupForEmitter("single emit", ({ EventEmitter, name }) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
emitter.on("hello", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
bench(name, () => {
|
||||
emitter.emit("hello", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
groupForEmitter("on x 10_000 (handler)", ({ EventEmitter, name }) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
bench(name, () => {
|
||||
var cb = event => {
|
||||
event.preventDefault();
|
||||
};
|
||||
emitter.on("hey", cb);
|
||||
var called = false;
|
||||
for (let i = 0; i < 10_000; i++)
|
||||
emitter.emit("hey", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
called = true;
|
||||
},
|
||||
});
|
||||
|
||||
if (!called) throw new Error("not called");
|
||||
});
|
||||
});
|
||||
|
||||
// for (let { impl: EventEmitter, name, monkey } of []) {
|
||||
// if (monkey) {
|
||||
// var monkeyEmitter = Object.assign({}, EventEmitter.prototype);
|
||||
// monkeyEmitter.on("hello", event => {
|
||||
// event.preventDefault();
|
||||
// });
|
||||
|
||||
// bench(`[monkey] ${className}.emit`, () => {
|
||||
// var called = false;
|
||||
// monkeyEmitter.emit("hello", {
|
||||
// preventDefault() {
|
||||
// id++;
|
||||
// called = true;
|
||||
// },
|
||||
// });
|
||||
|
||||
// if (!called) {
|
||||
// throw new Error("monkey failed");
|
||||
// }
|
||||
// });
|
||||
|
||||
// bench(`[monkey] ${className}.on x 10_000 (handler)`, () => {
|
||||
// var cb = () => {
|
||||
// event.preventDefault();
|
||||
// };
|
||||
// monkeyEmitter.on("hey", cb);
|
||||
// for (let i = 0; i < 10_000; i++)
|
||||
// monkey.emit("hey", {
|
||||
// preventDefault() {
|
||||
// id++;
|
||||
// },
|
||||
// });
|
||||
// monkeyEmitter.off("hey", cb);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// var target = new EventTarget();
|
||||
// target.addEventListener("hello", event => {});
|
||||
// bench("EventTarget.dispatch", () => {
|
||||
// target.dispatchEvent(event);
|
||||
// });
|
||||
|
||||
// var hey = new Event("hey");
|
||||
|
||||
// bench("EventTarget.on x 10_000 (handler)", () => {
|
||||
// var handler = event => {};
|
||||
// target.addEventListener("hey", handler);
|
||||
|
||||
// for (let i = 0; i < 10_000; i++) target.dispatchEvent(hey);
|
||||
// target.removeEventListener("hey", handler);
|
||||
// });
|
||||
|
||||
await run();
|
||||
@@ -1,40 +0,0 @@
|
||||
import { bench, run } from "mitata";
|
||||
import { groupForEmitter } from "./implementations.mjs";
|
||||
|
||||
var id = 0;
|
||||
|
||||
groupForEmitter("test 1", ({ EventEmitter, name }) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
emitter.on("hello", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
bench(name, () => {
|
||||
emitter.once("hello", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
emitter.emit("hello", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
groupForEmitter("test 2", ({ EventEmitter, name }) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
bench(name, () => {
|
||||
emitter.once("hello", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
emitter.emit("hello", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await run();
|
||||
@@ -1,63 +0,0 @@
|
||||
import { bench, run } from "mitata";
|
||||
import { groupForEmitter } from "./implementations.mjs";
|
||||
|
||||
// Psuedo RNG is derived from https://stackoverflow.com/a/424445
|
||||
let rngState = 123456789;
|
||||
function nextInt() {
|
||||
const m = 0x80000000; // 2**31;
|
||||
const a = 1103515245;
|
||||
const c = 12345;
|
||||
rngState = (a * rngState + c) % m;
|
||||
return rngState;
|
||||
}
|
||||
function nextRange(start, end) {
|
||||
// returns in range [start, end): including start, excluding end
|
||||
// can't modulu nextInt because of weak randomness in lower bits
|
||||
const rangeSize = end - start;
|
||||
const randomUnder1 = nextInt() / 0x7fffffff; // 2**31 - 1
|
||||
return start + Math.floor(randomUnder1 * rangeSize);
|
||||
}
|
||||
|
||||
const chunks = new Array(1024).fill(null).map((_, j) => {
|
||||
const arr = new Uint8Array(1024);
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
arr[i] = nextRange(0, 256);
|
||||
}
|
||||
return arr;
|
||||
});
|
||||
|
||||
groupForEmitter("stream simulation", ({ EventEmitter, name }) => {
|
||||
bench(name, () => {
|
||||
let id = 0;
|
||||
const stream = new EventEmitter();
|
||||
|
||||
stream.on("start", res => {
|
||||
if (res.status !== 200) throw new Error("not 200");
|
||||
});
|
||||
|
||||
const recived = [];
|
||||
stream.on("data", req => {
|
||||
recived.push(req);
|
||||
});
|
||||
|
||||
stream.on("end", ev => {
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
// simulate a stream
|
||||
stream.emit("start", { status: 200 });
|
||||
for (let chunk of chunks) {
|
||||
stream.emit("data", chunk);
|
||||
}
|
||||
stream.emit("end", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
},
|
||||
});
|
||||
|
||||
if (id !== 1) throw new Error("not implemented right");
|
||||
if (recived.length !== 1024) throw new Error("not implemented right");
|
||||
});
|
||||
});
|
||||
|
||||
await run();
|
||||
@@ -1,13 +1,11 @@
|
||||
{
|
||||
"name": "bench",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.16.10",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@swc/core": "^1.2.133",
|
||||
"benchmark": "^2.1.4",
|
||||
"mitata": "^0.1.6",
|
||||
"esbuild": "^0.14.12",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"mitata": "^0.1.6"
|
||||
"@swc/core": "^1.2.133",
|
||||
"@babel/core": "^7.16.10",
|
||||
"@babel/preset-react": "^7.16.7"
|
||||
},
|
||||
"scripts": {
|
||||
"ffi": "cd ffi && bun run deps && bun run build && bun run bench",
|
||||
|
||||
BIN
bench/snippets/bun.lockb
Executable file
BIN
bench/snippets/bun.lockb
Executable file
Binary file not shown.
101
bench/snippets/emitter.mjs
Normal file
101
bench/snippets/emitter.mjs
Normal file
@@ -0,0 +1,101 @@
|
||||
// **so this file can run in node**
|
||||
import { createRequire } from "node:module";
|
||||
const require = createRequire(import.meta.url);
|
||||
// --
|
||||
|
||||
const EventEmitterNative = require("node:events").EventEmitter;
|
||||
const TypedEmitter = require("tiny-typed-emitter").TypedEmitter;
|
||||
const EventEmitter3 = require("eventemitter3").EventEmitter;
|
||||
import { bench, run } from "../../node_modules/mitata/src/cli.mjs";
|
||||
const event = new Event("hello");
|
||||
var id = 0;
|
||||
for (let [EventEmitter, className] of [
|
||||
[EventEmitterNative, "EventEmitter"],
|
||||
[TypedEmitter, "TypedEmitter"],
|
||||
[EventEmitter3, "EventEmitter3"],
|
||||
]) {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
emitter.on("hello", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
bench(`${className}.emit`, () => {
|
||||
emitter.emit("hello", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
bench(`${className}.on x 10_000 (handler)`, () => {
|
||||
var cb = event => {
|
||||
event.preventDefault();
|
||||
};
|
||||
emitter.on("hey", cb);
|
||||
var called = false;
|
||||
for (let i = 0; i < 10_000; i++)
|
||||
emitter.emit("hey", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
called = true;
|
||||
},
|
||||
});
|
||||
emitter.off("hey", cb);
|
||||
|
||||
if (!called) throw new Error("not called");
|
||||
});
|
||||
|
||||
if (EventEmitter !== EventEmitter3) {
|
||||
var monkey = Object.assign({}, EventEmitter.prototype);
|
||||
monkey.on("hello", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
bench(`[monkey] ${className}.emit`, () => {
|
||||
var called = false;
|
||||
monkey.emit("hello", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
called = true;
|
||||
},
|
||||
});
|
||||
|
||||
if (!called) {
|
||||
throw new Error("monkey failed");
|
||||
}
|
||||
});
|
||||
|
||||
bench(`[monkey] ${className}.on x 10_000 (handler)`, () => {
|
||||
var cb = () => {
|
||||
event.preventDefault();
|
||||
};
|
||||
monkey.on("hey", cb);
|
||||
for (let i = 0; i < 10_000; i++)
|
||||
monkey.emit("hey", {
|
||||
preventDefault() {
|
||||
id++;
|
||||
},
|
||||
});
|
||||
monkey.off("hey", cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var target = new EventTarget();
|
||||
target.addEventListener("hello", event => {});
|
||||
bench("EventTarget.dispatch", () => {
|
||||
target.dispatchEvent(event);
|
||||
});
|
||||
|
||||
var hey = new Event("hey");
|
||||
|
||||
bench("EventTarget.on x 10_000 (handler)", () => {
|
||||
var handler = event => {};
|
||||
target.addEventListener("hey", handler);
|
||||
|
||||
for (let i = 0; i < 10_000; i++) target.dispatchEvent(hey);
|
||||
target.removeEventListener("hey", handler);
|
||||
});
|
||||
|
||||
await run();
|
||||
7
bench/snippets/package.json
Normal file
7
bench/snippets/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.0",
|
||||
"tiny-typed-emitter": "latest"
|
||||
},
|
||||
"prettier": "../../.prettierrc.cjs"
|
||||
}
|
||||
@@ -81,7 +81,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
|
||||
|
||||
- {% anchor id="node_events" %} [`node:events`](https://nodejs.org/api/events.html) {% /anchor %}
|
||||
- 🟡
|
||||
- Missing `EventEmitterAsyncResource` `events.on`.
|
||||
- Missing `EventEmitterAsyncResource`. `EventEmitter` is missing `{get}set}MaxListeners` `usingDomains` `init`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,464 +0,0 @@
|
||||
// Reimplementation of https://nodejs.org/api/events.html
|
||||
// Reference: https://github.com/nodejs/node/blob/main/lib/events.js
|
||||
var { isPromise, Array, Object } = import.meta.primordials;
|
||||
const SymbolFor = Symbol.for;
|
||||
const ObjectDefineProperty = Object.defineProperty;
|
||||
const kCapture = Symbol("kCapture");
|
||||
const kErrorMonitor = SymbolFor("events.errorMonitor");
|
||||
const kMaxEventTargetListeners = Symbol("events.maxEventTargetListeners");
|
||||
const kMaxEventTargetListenersWarned = Symbol("events.maxEventTargetListenersWarned");
|
||||
const kWatermarkData = SymbolFor("nodejs.watermarkData");
|
||||
const kRejection = SymbolFor("nodejs.rejection");
|
||||
const captureRejectionSymbol = SymbolFor("nodejs.rejection");
|
||||
const ArrayPrototypeSlice = Array.prototype.slice;
|
||||
|
||||
var defaultMaxListeners = 10;
|
||||
|
||||
// EventEmitter must be a standard function because some old code will do weird tricks like `EventEmitter.apply(this)`.
|
||||
function EventEmitter(opts) {
|
||||
if (this._events === undefined || this._events === this.__proto__._events) {
|
||||
this._events = { __proto__: null };
|
||||
this._eventsCount = 0;
|
||||
}
|
||||
|
||||
this._maxListeners ??= undefined;
|
||||
if (
|
||||
(this[kCapture] = opts?.captureRejections ? Boolean(opts?.captureRejections) : EventEmitter.prototype[kCapture])
|
||||
) {
|
||||
this.emit = emitWithRejectionCapture;
|
||||
}
|
||||
}
|
||||
|
||||
EventEmitter.prototype._events = undefined;
|
||||
EventEmitter.prototype._eventsCount = 0;
|
||||
EventEmitter.prototype._maxListeners = undefined;
|
||||
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
|
||||
validateNumber(n, "setMaxListeners", 0);
|
||||
this._maxListeners = n;
|
||||
return this;
|
||||
};
|
||||
|
||||
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
|
||||
return this._maxListeners ?? defaultMaxListeners;
|
||||
};
|
||||
|
||||
function emitError(emitter, args) {
|
||||
var { _events: events } = emitter;
|
||||
args[0] ??= new Error("Unhandled error.");
|
||||
if (!events) throw args[0];
|
||||
var errorMonitor = events[kErrorMonitor];
|
||||
if (errorMonitor) {
|
||||
for (var handler of ArrayPrototypeSlice.call(errorMonitor)) {
|
||||
handler.apply(emitter, args);
|
||||
}
|
||||
}
|
||||
var handlers = events.error;
|
||||
if (!handlers) throw args[0];
|
||||
for (var handler of ArrayPrototypeSlice.call(handlers)) {
|
||||
handler.apply(emitter, args);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function addCatch(emitter, promise, type, args) {
|
||||
promise.then(undefined, function (err) {
|
||||
// The callback is called with nextTick to avoid a follow-up rejection from this promise.
|
||||
process.nextTick(emitUnhandledRejectionOrErr, emitter, err, type, args);
|
||||
});
|
||||
}
|
||||
|
||||
function emitUnhandledRejectionOrErr(emitter, err, type, args) {
|
||||
if (typeof emitter[kRejection] === "function") {
|
||||
emitter[kRejection](err, type, ...args);
|
||||
} else {
|
||||
// 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 {
|
||||
emitter[kCapture] = false;
|
||||
emitter.emit("error", err);
|
||||
} finally {
|
||||
emitter[kCapture] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const emitWithoutRejectionCapture = function emit(type, ...args) {
|
||||
if (type === "error") {
|
||||
return emitError(this, args);
|
||||
}
|
||||
var { _events: events } = this;
|
||||
if (events === undefined) return false;
|
||||
var handlers = events[type];
|
||||
if (handlers === undefined) return false;
|
||||
|
||||
for (var handler of [...handlers]) {
|
||||
handler.apply(this, args);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const emitWithRejectionCapture = function emit(type, ...args) {
|
||||
if (type === "error") {
|
||||
return emitError(this, args);
|
||||
}
|
||||
var { _events: events } = this;
|
||||
if (events === undefined) return false;
|
||||
var handlers = events[type];
|
||||
if (handlers === undefined) return false;
|
||||
for (var handler of [...handlers]) {
|
||||
var result = handler.apply(this, args);
|
||||
if (result !== undefined && isPromise(result)) {
|
||||
addCatch(this, result, type, args);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
EventEmitter.prototype.emit = emitWithoutRejectionCapture;
|
||||
|
||||
EventEmitter.prototype.addListener = function addListener(type, fn) {
|
||||
checkListener(fn);
|
||||
var events = this._events;
|
||||
if (!events) {
|
||||
events = this._events = { __proto__: null };
|
||||
this._eventsCount = 0;
|
||||
} else if (events.newListener) {
|
||||
this.emit("newListener", type, fn.listener ?? fn);
|
||||
}
|
||||
var handlers = events[type];
|
||||
if (!handlers) {
|
||||
events[type] = [fn];
|
||||
this._eventsCount++;
|
||||
} else {
|
||||
handlers.push(fn);
|
||||
var m = this._maxListeners ?? defaultMaxListeners;
|
||||
if (m > 0 && handlers.length > m && !handlers.warned) {
|
||||
overflowWarning(this, type, handlers);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
|
||||
|
||||
EventEmitter.prototype.prependListener = function prependListener(type, fn) {
|
||||
checkListener(fn);
|
||||
var events = this._events;
|
||||
if (!events) {
|
||||
events = this._events = { __proto__: null };
|
||||
this._eventsCount = 0;
|
||||
} else if (events.newListener) {
|
||||
this.emit("newListener", type, fn.listener ?? fn);
|
||||
}
|
||||
var handlers = events[type];
|
||||
if (!handlers) {
|
||||
events[type] = [fn];
|
||||
this._eventsCount++;
|
||||
} else {
|
||||
handlers.unshift(fn);
|
||||
var m = this._maxListeners ?? defaultMaxListeners;
|
||||
if (m > 0 && handlers.length > m && !handlers.warned) {
|
||||
overflowWarning(this, type, handlers);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
function overflowWarning(emitter, type, handlers) {
|
||||
handlers.warned = true;
|
||||
const warn = new Error(
|
||||
`Possible EventEmitter memory leak detected. ${handlers.length} ${String(type)} listeners ` +
|
||||
`added to [${emitter.constructor.name}]. Use emitter.setMaxListeners() to increase limit`,
|
||||
);
|
||||
warn.name = "MaxListenersExceededWarning";
|
||||
warn.emitter = emitter;
|
||||
warn.type = type;
|
||||
warn.count = handlers.length;
|
||||
process.emitWarning(warn);
|
||||
}
|
||||
|
||||
function onceWrapper(type, listener, ...args) {
|
||||
this.removeListener(type, listener);
|
||||
listener.apply(this, args);
|
||||
}
|
||||
|
||||
EventEmitter.prototype.once = function once(type, fn) {
|
||||
checkListener(fn);
|
||||
const bound = onceWrapper.bind(this, type, fn);
|
||||
bound.listener = fn;
|
||||
this.addListener(type, bound);
|
||||
return this;
|
||||
};
|
||||
|
||||
EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, fn) {
|
||||
checkListener(fn);
|
||||
const bound = onceWrapper.bind(this, type, fn);
|
||||
bound.listener = fn;
|
||||
this.prependListener(type, bound);
|
||||
return this;
|
||||
};
|
||||
|
||||
EventEmitter.prototype.removeListener = function removeListener(type, fn) {
|
||||
checkListener(fn);
|
||||
var { _events: events } = this;
|
||||
if (!events) return this;
|
||||
var handlers = events[type];
|
||||
if (!handlers) return this;
|
||||
var length = handlers.length;
|
||||
let position = -1;
|
||||
for (let i = length - 1; i >= 0; i--) {
|
||||
if (handlers[i] === fn || handlers[i].listener === fn) {
|
||||
position = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (position < 0) return this;
|
||||
if (position === 0) {
|
||||
handlers.shift();
|
||||
} else {
|
||||
handlers.splice(position, 1);
|
||||
}
|
||||
if (handlers.length === 0) {
|
||||
delete events[type];
|
||||
this._eventsCount--;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
|
||||
|
||||
EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
|
||||
var { _events: events } = this;
|
||||
if (type && events) {
|
||||
if (events[type]) {
|
||||
delete events[type];
|
||||
this._eventsCount--;
|
||||
}
|
||||
} else {
|
||||
this._events = { __proto__: null };
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
EventEmitter.prototype.listeners = function listeners(type) {
|
||||
var { _events: events } = this;
|
||||
if (!events) return [];
|
||||
var handlers = events[type];
|
||||
if (!handlers) return [];
|
||||
return handlers.map(x => x.listener ?? x);
|
||||
};
|
||||
|
||||
EventEmitter.prototype.rawListeners = function rawListeners(type) {
|
||||
var { _events } = this;
|
||||
if (!_events) return [];
|
||||
var handlers = _events[type];
|
||||
if (!handlers) return [];
|
||||
return handlers.slice();
|
||||
};
|
||||
|
||||
EventEmitter.prototype.listenerCount = function listenerCount(type) {
|
||||
var { _events: events } = this;
|
||||
if (!events) return 0;
|
||||
return events[type]?.length ?? 0;
|
||||
};
|
||||
|
||||
EventEmitter.prototype.eventNames = function eventNames() {
|
||||
return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
|
||||
};
|
||||
|
||||
EventEmitter.prototype[kCapture] = false;
|
||||
|
||||
function once(emitter, type, { signal } = {}) {
|
||||
validateAbortSignal(signal, "options.signal");
|
||||
if (signal?.aborted) {
|
||||
throw new AbortError(undefined, { cause: signal?.reason });
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const errorListener = err => {
|
||||
emitter.removeListener(type, 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);
|
||||
};
|
||||
eventTargetAgnosticAddListener(emitter, type, resolver, { once: true });
|
||||
if (type !== "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, type, resolver);
|
||||
eventTargetAgnosticRemoveListener(emitter, "error", errorListener);
|
||||
reject(new AbortError(undefined, { cause: signal?.reason }));
|
||||
}
|
||||
if (signal != null) {
|
||||
eventTargetAgnosticAddListener(signal, "abort", abortListener, { once: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
EventEmitter.once = once;
|
||||
|
||||
function on(emitter, type, { signal, close, highWatermark = Number.MAX_SAFE_INTEGER, lowWatermark = 1 } = {}) {
|
||||
throw new Error("events.on is not implemented. See https://github.com/oven-sh/bun/issues/2679");
|
||||
}
|
||||
EventEmitter.on = on;
|
||||
|
||||
function getEventListeners(emitter, type) {
|
||||
if (emitter instanceof EventTarget) {
|
||||
throw new Error(
|
||||
"getEventListeners with an EventTarget is not implemented. See https://github.com/oven-sh/bun/issues/2678",
|
||||
);
|
||||
}
|
||||
return emitter.listeners(type);
|
||||
}
|
||||
EventEmitter.getEventListeners = getEventListeners;
|
||||
|
||||
function setMaxListeners(n, ...eventTargets) {
|
||||
validateNumber(n, "setMaxListeners", 0);
|
||||
var length;
|
||||
if (eventTargets && (length = eventTargets.length)) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
eventTargets[i].setMaxListeners(n);
|
||||
}
|
||||
} else {
|
||||
defaultMaxListeners = n;
|
||||
}
|
||||
}
|
||||
EventEmitter.setMaxListeners = setMaxListeners;
|
||||
|
||||
function listenerCount(emitter, type) {
|
||||
return emitter.listenerCount(type);
|
||||
}
|
||||
EventEmitter.listenerCount = listenerCount;
|
||||
|
||||
EventEmitter.EventEmitter = EventEmitter;
|
||||
EventEmitter.usingDomains = false;
|
||||
EventEmitter.captureRejectionSymbol = captureRejectionSymbol;
|
||||
ObjectDefineProperty(EventEmitter, "captureRejections", {
|
||||
__proto__: null,
|
||||
get() {
|
||||
return EventEmitter.prototype[kCapture];
|
||||
},
|
||||
set(value) {
|
||||
validateBoolean(value, "EventEmitter.captureRejections");
|
||||
|
||||
EventEmitter.prototype[kCapture] = value;
|
||||
},
|
||||
enumerable: true,
|
||||
});
|
||||
EventEmitter.errorMonitor = kErrorMonitor;
|
||||
Object.defineProperties(EventEmitter, {
|
||||
defaultMaxListeners: {
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
return defaultMaxListeners;
|
||||
},
|
||||
set: arg => {
|
||||
validateNumber(arg, "defaultMaxListeners", 0);
|
||||
defaultMaxListeners = arg;
|
||||
},
|
||||
},
|
||||
kMaxEventTargetListeners: {
|
||||
__proto__: null,
|
||||
value: kMaxEventTargetListeners,
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
},
|
||||
kMaxEventTargetListenersWarned: {
|
||||
__proto__: null,
|
||||
value: kMaxEventTargetListenersWarned,
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
},
|
||||
});
|
||||
EventEmitter.init = EventEmitter;
|
||||
EventEmitter[Symbol.for("CommonJS")] = 0;
|
||||
|
||||
export default EventEmitter;
|
||||
|
||||
function eventTargetAgnosticRemoveListener(emitter, name, listener, flags) {
|
||||
if (typeof emitter.removeListener === "function") {
|
||||
emitter.removeListener(name, listener);
|
||||
} else {
|
||||
emitter.removeEventListener(name, listener, flags);
|
||||
}
|
||||
}
|
||||
|
||||
function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
|
||||
if (typeof emitter.on === "function") {
|
||||
emitter.on(name, listener);
|
||||
} else {
|
||||
emitter.addEventListener(name, listener);
|
||||
}
|
||||
}
|
||||
|
||||
class AbortError extends Error {
|
||||
constructor(message = "The operation was aborted", options = undefined) {
|
||||
if (options !== undefined && typeof options !== "object") {
|
||||
throw new codes.ERR_INVALID_ARG_TYPE("options", "Object", options);
|
||||
}
|
||||
super(message, options);
|
||||
this.code = "ABORT_ERR";
|
||||
this.name = "AbortError";
|
||||
}
|
||||
}
|
||||
|
||||
function ERR_INVALID_ARG_TYPE(name, type, value) {
|
||||
const err = new TypeError(`The "${name}" argument must be of type ${type}. Received ${value}`);
|
||||
err.code = "ERR_INVALID_ARG_TYPE";
|
||||
return err;
|
||||
}
|
||||
|
||||
function ERR_OUT_OF_RANGE(name, range, value) {
|
||||
const err = new RangeError(`The "${name}" argument is out of range. It must be ${range}. Received ${value}`);
|
||||
err.code = "ERR_OUT_OF_RANGE";
|
||||
return err;
|
||||
}
|
||||
|
||||
function validateAbortSignal(signal, name) {
|
||||
if (signal !== undefined && (signal === null || typeof signal !== "object" || !("aborted" in signal))) {
|
||||
throw new ERR_INVALID_ARG_TYPE(name, "AbortSignal", signal);
|
||||
}
|
||||
}
|
||||
|
||||
function validateNumber(value, name, min = undefined, max) {
|
||||
if (typeof value !== "number") throw new ERR_INVALID_ARG_TYPE(name, "number", value);
|
||||
if (
|
||||
(min != null && value < min) ||
|
||||
(max != null && value > max) ||
|
||||
((min != null || max != null) && Number.isNaN(value))
|
||||
) {
|
||||
throw new ERR_OUT_OF_RANGE(
|
||||
name,
|
||||
`${min != null ? `>= ${min}` : ""}${min != null && max != null ? " && " : ""}${max != null ? `<= ${max}` : ""}`,
|
||||
value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function checkListener(listener) {
|
||||
if (typeof listener !== "function") {
|
||||
throw new TypeError("The listener must be a function");
|
||||
}
|
||||
}
|
||||
|
||||
export class EventEmitterAsyncResource extends EventEmitter {
|
||||
constructor(options = undefined) {
|
||||
throw new Error("EventEmitterAsyncResource is not implemented. See https://github.com/oven-sh/bun/issues/2681");
|
||||
}
|
||||
}
|
||||
|
||||
EventEmitter.EventEmitterAsyncResource = EventEmitterAsyncResource;
|
||||
@@ -1712,15 +1712,7 @@ pub const ModuleLoader = struct {
|
||||
.@"node:buffer" => return jsSyntheticModule(.@"node:buffer"),
|
||||
.@"node:string_decoder" => return jsSyntheticModule(.@"node:string_decoder"),
|
||||
.@"node:module" => return jsSyntheticModule(.@"node:module"),
|
||||
.@"node:events" => {
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = ZigString.init(jsModuleFromFile(jsc_vm.load_builtins_from_path, "events.exports.js")),
|
||||
.specifier = ZigString.init("node:events"),
|
||||
.source_url = ZigString.init("node:events"),
|
||||
.hash = 0,
|
||||
};
|
||||
},
|
||||
.@"node:events" => return jsSyntheticModule(.@"node:events"),
|
||||
.@"node:process" => return jsSyntheticModule(.@"node:process"),
|
||||
.@"node:tty" => return jsSyntheticModule(.@"node:tty"),
|
||||
.@"node:util/types" => return jsSyntheticModule(.@"node:util/types"),
|
||||
|
||||
BIN
test/bun.lockb
BIN
test/bun.lockb
Binary file not shown.
@@ -1,474 +1,78 @@
|
||||
import { test, describe, expect } from "bun:test";
|
||||
import { sleep } from "bun";
|
||||
|
||||
import { test, describe, expect, it } from "bun:test";
|
||||
import { heapStats } from "bun:jsc";
|
||||
import { expectMaxObjectTypeCount, gc } from "harness";
|
||||
// this is also testing that imports with default and named imports in the same statement work
|
||||
// our transpiler transform changes this to a var with import.meta.require
|
||||
import EventEmitter, { getEventListeners, captureRejectionSymbol } from "node:events";
|
||||
|
||||
describe("node:events", () => {
|
||||
test("captureRejectionSymbol", () => {
|
||||
describe("EventEmitter", () => {
|
||||
it("captureRejectionSymbol", () => {
|
||||
expect(EventEmitter.captureRejectionSymbol).toBeDefined();
|
||||
expect(captureRejectionSymbol).toBeDefined();
|
||||
expect(captureRejectionSymbol).toBe(EventEmitter.captureRejectionSymbol);
|
||||
});
|
||||
|
||||
test("once", done => {
|
||||
const emitter = new EventEmitter();
|
||||
EventEmitter.once(emitter, "hey").then(x => {
|
||||
try {
|
||||
expect(x).toEqual([1, 5]);
|
||||
} catch (error) {
|
||||
done(error);
|
||||
}
|
||||
done();
|
||||
});
|
||||
emitter.emit("hey", 1, 5);
|
||||
});
|
||||
|
||||
test("once (abort)", done => {
|
||||
const emitter = new EventEmitter();
|
||||
const controller = new AbortController();
|
||||
EventEmitter.once(emitter, "hey", { signal: controller.signal })
|
||||
.then(() => done(new Error("Should not be called")))
|
||||
.catch(() => done());
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
test("once (two events in same tick)", done => {
|
||||
const emitter = new EventEmitter();
|
||||
EventEmitter.once(emitter, "hey").then(() => {
|
||||
EventEmitter.once(emitter, "hey").then(data => {
|
||||
try {
|
||||
expect(data).toEqual([3]);
|
||||
} catch (error) {
|
||||
done(error);
|
||||
}
|
||||
done();
|
||||
});
|
||||
setTimeout(() => {
|
||||
emitter.emit("hey", 3);
|
||||
}, 10);
|
||||
});
|
||||
emitter.emit("hey", 1);
|
||||
emitter.emit("hey", 2);
|
||||
});
|
||||
|
||||
// TODO: extensive events.on tests
|
||||
// test("on", () => {
|
||||
// const emitter = new EventEmitter();
|
||||
// const asyncIterator = EventEmitter.on(emitter, "hey");
|
||||
|
||||
// expect(asyncIterator.next).toBeDefined();
|
||||
// expect(asyncIterator[Symbol.asyncIterator]).toBeDefined();
|
||||
|
||||
// const fn = async () => {
|
||||
// const { value } = await asyncIterator.next();
|
||||
// expect(value).toBe(1);
|
||||
// };
|
||||
|
||||
// emitter.emit("hey", 1, 2, 3);
|
||||
// });
|
||||
});
|
||||
|
||||
describe("EventEmitter", () => {
|
||||
test("getEventListeners", () => {
|
||||
expect(getEventListeners(new EventEmitter(), "hey").length).toBe(0);
|
||||
});
|
||||
|
||||
test("constructor", () => {
|
||||
test("EventEmitter constructor", () => {
|
||||
var emitter = new EventEmitter();
|
||||
emitter.setMaxListeners(100);
|
||||
expect(emitter.getMaxListeners()).toBe(100);
|
||||
});
|
||||
|
||||
test("removeAllListeners()", () => {
|
||||
var emitter = new EventEmitter() as any;
|
||||
var ran = false;
|
||||
emitter.on("hey", () => {
|
||||
ran = true;
|
||||
});
|
||||
emitter.on("hey", () => {
|
||||
ran = true;
|
||||
});
|
||||
emitter.on("exit", () => {
|
||||
ran = true;
|
||||
});
|
||||
const { _events } = emitter;
|
||||
emitter.removeAllListeners();
|
||||
expect(emitter.listenerCount("hey")).toBe(0);
|
||||
expect(emitter.listenerCount("exit")).toBe(0);
|
||||
emitter.emit("hey");
|
||||
emitter.emit("exit");
|
||||
expect(ran).toBe(false);
|
||||
expect(_events).not.toBe(emitter._events); // This looks wrong but node.js replaces it too
|
||||
emitter.on("hey", () => {
|
||||
ran = true;
|
||||
});
|
||||
emitter.emit("hey");
|
||||
expect(ran).toBe(true);
|
||||
expect(emitter.listenerCount("hey")).toBe(1);
|
||||
});
|
||||
|
||||
test("removeAllListeners(type)", () => {
|
||||
test("EventEmitter.removeAllListeners()", () => {
|
||||
var emitter = new EventEmitter();
|
||||
var ran = false;
|
||||
emitter.on("hey", () => {
|
||||
ran = true;
|
||||
});
|
||||
emitter.on("exit", () => {
|
||||
ran = true;
|
||||
});
|
||||
expect(emitter.listenerCount("hey")).toBe(1);
|
||||
emitter.removeAllListeners("hey");
|
||||
emitter.removeAllListeners();
|
||||
expect(emitter.listenerCount("hey")).toBe(0);
|
||||
expect(emitter.listenerCount("exit")).toBe(1);
|
||||
emitter.emit("hey");
|
||||
expect(ran).toBe(false);
|
||||
emitter.emit("exit");
|
||||
emitter.on("hey", () => {
|
||||
ran = true;
|
||||
});
|
||||
emitter.emit("hey");
|
||||
expect(ran).toBe(true);
|
||||
expect(emitter.listenerCount("hey")).toBe(1);
|
||||
});
|
||||
|
||||
// These are also tests for the done() function in the test runner.
|
||||
describe("emit", () => {
|
||||
test("different tick", done => {
|
||||
var emitter = new EventEmitter();
|
||||
emitter.on("wow", () => done());
|
||||
queueMicrotask(() => {
|
||||
emitter.emit("wow");
|
||||
});
|
||||
});
|
||||
|
||||
// Unlike Jest, bun supports async and done
|
||||
test("async microtask before", done => {
|
||||
(async () => {
|
||||
await 1;
|
||||
var emitter = new EventEmitter();
|
||||
emitter.on("wow", () => done());
|
||||
emitter.emit("wow");
|
||||
})();
|
||||
});
|
||||
|
||||
test("async microtask after", done => {
|
||||
(async () => {
|
||||
var emitter = new EventEmitter();
|
||||
emitter.on("wow", () => done());
|
||||
await 1;
|
||||
emitter.emit("wow");
|
||||
})();
|
||||
});
|
||||
|
||||
test("same tick", done => {
|
||||
var emitter = new EventEmitter();
|
||||
|
||||
emitter.on("wow", () => done());
|
||||
|
||||
test("EventEmitter emit (different tick)", done => {
|
||||
var emitter = new EventEmitter();
|
||||
emitter.on("wow", () => done());
|
||||
queueMicrotask(() => {
|
||||
emitter.emit("wow");
|
||||
});
|
||||
|
||||
test("setTimeout task", done => {
|
||||
var emitter = new EventEmitter();
|
||||
emitter.on("wow", () => done());
|
||||
setTimeout(() => emitter.emit("wow"), 1);
|
||||
});
|
||||
});
|
||||
|
||||
test("addListener return type", () => {
|
||||
var myEmitter = new EventEmitter();
|
||||
expect(myEmitter.addListener("foo", () => {})).toBe(myEmitter);
|
||||
// Unlike Jest, bun supports async and done
|
||||
test("async EventEmitter emit (microtask)", async done => {
|
||||
await 1;
|
||||
var emitter = new EventEmitter();
|
||||
emitter.on("wow", () => done());
|
||||
emitter.emit("wow");
|
||||
});
|
||||
|
||||
test("addListener validates function", () => {
|
||||
var myEmitter = new EventEmitter();
|
||||
expect(() => myEmitter.addListener("foo", {} as any)).toThrow();
|
||||
test("async EventEmitter emit (microtask) after", async done => {
|
||||
var emitter = new EventEmitter();
|
||||
emitter.on("wow", () => done());
|
||||
await 1;
|
||||
emitter.emit("wow");
|
||||
});
|
||||
|
||||
test("removeListener return type", () => {
|
||||
var myEmitter = new EventEmitter();
|
||||
expect(myEmitter.removeListener("foo", () => {})).toBe(myEmitter);
|
||||
test("EventEmitter emit (same tick)", done => {
|
||||
var emitter = new EventEmitter();
|
||||
|
||||
emitter.on("wow", () => done());
|
||||
|
||||
emitter.emit("wow");
|
||||
});
|
||||
|
||||
test("once", () => {
|
||||
var myEmitter = new EventEmitter();
|
||||
var calls = 0;
|
||||
|
||||
const fn = () => {
|
||||
calls++;
|
||||
};
|
||||
|
||||
myEmitter.once("foo", fn);
|
||||
|
||||
expect(myEmitter.listenerCount("foo")).toBe(1);
|
||||
expect(myEmitter.listeners("foo")).toEqual([fn]);
|
||||
|
||||
myEmitter.emit("foo");
|
||||
myEmitter.emit("foo");
|
||||
|
||||
expect(calls).toBe(1);
|
||||
expect(myEmitter.listenerCount("foo")).toBe(0);
|
||||
});
|
||||
|
||||
test("addListener/removeListener aliases", () => {
|
||||
expect(EventEmitter.prototype.addListener).toBe(EventEmitter.prototype.on);
|
||||
expect(EventEmitter.prototype.removeListener).toBe(EventEmitter.prototype.off);
|
||||
});
|
||||
|
||||
test("prependListener", () => {
|
||||
const myEmitter = new EventEmitter();
|
||||
const order: number[] = [];
|
||||
|
||||
myEmitter.on("foo", () => {
|
||||
order.push(1);
|
||||
});
|
||||
|
||||
myEmitter.prependListener("foo", () => {
|
||||
order.push(2);
|
||||
});
|
||||
|
||||
myEmitter.prependListener("foo", () => {
|
||||
order.push(3);
|
||||
});
|
||||
|
||||
myEmitter.on("foo", () => {
|
||||
order.push(4);
|
||||
});
|
||||
|
||||
myEmitter.emit("foo");
|
||||
|
||||
expect(order).toEqual([3, 2, 1, 4]);
|
||||
});
|
||||
|
||||
test("prependOnceListener", () => {
|
||||
const myEmitter = new EventEmitter();
|
||||
const order: number[] = [];
|
||||
|
||||
myEmitter.on("foo", () => {
|
||||
order.push(1);
|
||||
});
|
||||
|
||||
myEmitter.prependOnceListener("foo", () => {
|
||||
order.push(2);
|
||||
});
|
||||
myEmitter.prependOnceListener("foo", () => {
|
||||
order.push(3);
|
||||
});
|
||||
|
||||
myEmitter.on("foo", () => {
|
||||
order.push(4);
|
||||
});
|
||||
|
||||
myEmitter.emit("foo");
|
||||
|
||||
expect(order).toEqual([3, 2, 1, 4]);
|
||||
|
||||
myEmitter.emit("foo");
|
||||
|
||||
expect(order).toEqual([3, 2, 1, 4, 1, 4]);
|
||||
});
|
||||
|
||||
test("listeners", () => {
|
||||
const myEmitter = new EventEmitter();
|
||||
const fn = () => {};
|
||||
myEmitter.on("foo", fn);
|
||||
expect(myEmitter.listeners("foo")).toEqual([fn]);
|
||||
const fn2 = () => {};
|
||||
myEmitter.on("foo", fn2);
|
||||
expect(myEmitter.listeners("foo")).toEqual([fn, fn2]);
|
||||
myEmitter.off("foo", fn2);
|
||||
expect(myEmitter.listeners("foo")).toEqual([fn]);
|
||||
});
|
||||
|
||||
test("rawListeners", () => {
|
||||
const myEmitter = new EventEmitter();
|
||||
const fn = () => {};
|
||||
myEmitter.on("foo", fn);
|
||||
expect(myEmitter.listeners("foo")).toEqual([fn]);
|
||||
const fn2 = () => {};
|
||||
myEmitter.on("foo", fn2);
|
||||
expect(myEmitter.listeners("foo")).toEqual([fn, fn2]);
|
||||
myEmitter.off("foo", fn2);
|
||||
expect(myEmitter.listeners("foo")).toEqual([fn]);
|
||||
});
|
||||
|
||||
test("eventNames", () => {
|
||||
const myEmitter = new EventEmitter();
|
||||
expect(myEmitter.eventNames()).toEqual([]);
|
||||
const fn = () => {};
|
||||
myEmitter.on("foo", fn);
|
||||
expect(myEmitter.eventNames()).toEqual(["foo"]);
|
||||
myEmitter.on("bar", () => {});
|
||||
expect(myEmitter.eventNames()).toEqual(["foo", "bar"]);
|
||||
myEmitter.off("foo", fn);
|
||||
expect(myEmitter.eventNames()).toEqual(["bar"]);
|
||||
});
|
||||
|
||||
test("_eventsCount", () => {
|
||||
const myEmitter = new EventEmitter() as EventEmitter & {
|
||||
_eventsCount: number;
|
||||
};
|
||||
expect(myEmitter._eventsCount).toBe(0);
|
||||
myEmitter.on("foo", () => {});
|
||||
expect(myEmitter._eventsCount).toBe(1);
|
||||
myEmitter.on("foo", () => {});
|
||||
expect(myEmitter._eventsCount).toBe(1);
|
||||
myEmitter.on("bar", () => {});
|
||||
expect(myEmitter._eventsCount).toBe(2);
|
||||
myEmitter.on("foo", () => {});
|
||||
expect(myEmitter._eventsCount).toBe(2);
|
||||
myEmitter.on("bar", () => {});
|
||||
expect(myEmitter._eventsCount).toBe(2);
|
||||
myEmitter.removeAllListeners("foo");
|
||||
expect(myEmitter._eventsCount).toBe(1);
|
||||
});
|
||||
|
||||
test("events.init", () => {
|
||||
// init is a undocumented property that is identical to the constructor except it doesn't return the instance
|
||||
// in node, EventEmitter just calls init()
|
||||
let instance = Object.create(EventEmitter.prototype);
|
||||
(EventEmitter as any).init.call(instance);
|
||||
expect(instance._eventsCount).toBe(0);
|
||||
expect(instance._maxListeners).toBeUndefined();
|
||||
expect(instance._events).toEqual({});
|
||||
expect(instance instanceof EventEmitter).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("EventEmitter error handling", () => {
|
||||
test("unhandled error event throws on emit", () => {
|
||||
const myEmitter = new EventEmitter();
|
||||
|
||||
expect(() => {
|
||||
myEmitter.emit("error", "Hello!");
|
||||
}).toThrow("Hello!");
|
||||
});
|
||||
|
||||
test("unhandled error event throws on emit with no arguments", () => {
|
||||
const myEmitter = new EventEmitter();
|
||||
|
||||
expect(() => {
|
||||
myEmitter.emit("error");
|
||||
}).toThrow("Unhandled error.");
|
||||
});
|
||||
|
||||
test("handled error event", () => {
|
||||
const myEmitter = new EventEmitter();
|
||||
|
||||
let handled = false;
|
||||
myEmitter.on("error", (...args) => {
|
||||
expect(args).toEqual(["Hello", "World"]);
|
||||
handled = true;
|
||||
});
|
||||
|
||||
myEmitter.emit("error", "Hello", "World");
|
||||
|
||||
expect(handled).toBe(true);
|
||||
});
|
||||
|
||||
test("errorMonitor", () => {
|
||||
const myEmitter = new EventEmitter();
|
||||
|
||||
let handled = false;
|
||||
myEmitter.on(EventEmitter.errorMonitor, (...args) => {
|
||||
expect(args).toEqual(["Hello", "World"]);
|
||||
handled = true;
|
||||
});
|
||||
|
||||
myEmitter.on("error", () => {});
|
||||
|
||||
myEmitter.emit("error", "Hello", "World");
|
||||
|
||||
expect(handled).toBe(true);
|
||||
});
|
||||
|
||||
test("errorMonitor (unhandled)", () => {
|
||||
const myEmitter = new EventEmitter();
|
||||
|
||||
let handled = false;
|
||||
myEmitter.on(EventEmitter.errorMonitor, (...args) => {
|
||||
expect(args).toEqual(["Hello", "World"]);
|
||||
handled = true;
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
myEmitter.emit("error", "Hello", "World");
|
||||
}).toThrow("Hello");
|
||||
|
||||
expect(handled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("EventEmitter captureRejections", () => {
|
||||
// Can't catch the unhandled rejection because we do not have process.on("unhandledRejection")
|
||||
// test("captureRejections off will not capture rejections", async () => {
|
||||
// const myEmitter = new EventEmitter();
|
||||
|
||||
// let handled = false;
|
||||
// myEmitter.on("error", (...args) => {
|
||||
// handled = true;
|
||||
// });
|
||||
|
||||
// myEmitter.on("action", async () => {
|
||||
// throw new Error("Hello World");
|
||||
// });
|
||||
|
||||
// myEmitter.emit("action");
|
||||
|
||||
// await sleep(1);
|
||||
|
||||
// expect(handled).toBe(false);
|
||||
// });
|
||||
test("it captures rejections", async () => {
|
||||
const myEmitter = new EventEmitter({ captureRejections: true });
|
||||
|
||||
let handled: any = null;
|
||||
myEmitter.on("error", (...args) => {
|
||||
handled = args;
|
||||
});
|
||||
|
||||
myEmitter.on("action", async () => {
|
||||
throw 123;
|
||||
});
|
||||
|
||||
myEmitter.emit("action");
|
||||
|
||||
await sleep(5);
|
||||
|
||||
expect(handled).toEqual([123]);
|
||||
});
|
||||
test("it does not capture successful promises", async () => {
|
||||
const myEmitter = new EventEmitter({ captureRejections: true });
|
||||
|
||||
let handled: any = null;
|
||||
myEmitter.on("error", () => {
|
||||
handled = true;
|
||||
});
|
||||
|
||||
myEmitter.on("action", async () => {
|
||||
return 123;
|
||||
});
|
||||
|
||||
myEmitter.emit("action");
|
||||
|
||||
await sleep(5);
|
||||
|
||||
expect(handled).toEqual(null);
|
||||
});
|
||||
test("it does not capture handled rejections", async () => {
|
||||
const myEmitter = new EventEmitter({ captureRejections: true });
|
||||
|
||||
let handled: any = null;
|
||||
myEmitter.on("error", () => {
|
||||
handled = true;
|
||||
});
|
||||
|
||||
myEmitter.on("action", async () => {
|
||||
return Promise.reject(123).catch(() => 234);
|
||||
});
|
||||
|
||||
myEmitter.emit("action");
|
||||
|
||||
await sleep(5);
|
||||
|
||||
expect(handled).toEqual(null);
|
||||
test("EventEmitter emit (setTimeout task)", done => {
|
||||
var emitter = new EventEmitter();
|
||||
emitter.on("wow", () => done());
|
||||
setTimeout(() => emitter.emit("wow"), 1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -508,30 +112,53 @@ const waysOfCreating = [
|
||||
},
|
||||
];
|
||||
|
||||
describe("EventEmitter constructors", () => {
|
||||
for (let create of waysOfCreating) {
|
||||
test(`${create
|
||||
.toString()
|
||||
.slice(6, 52)
|
||||
.replaceAll("\n", "")
|
||||
.trim()
|
||||
.replaceAll(/ {2,}/g, " ")
|
||||
.replace(/^\{ ?/, "")} should work`, () => {
|
||||
var myEmitter = create();
|
||||
var called = false;
|
||||
(myEmitter as EventEmitter).once("event", function () {
|
||||
called = true;
|
||||
// @ts-ignore
|
||||
expect(this).toBe(myEmitter);
|
||||
});
|
||||
var firstEvents = myEmitter._events;
|
||||
expect(myEmitter.listenerCount("event")).toBe(1);
|
||||
|
||||
expect(myEmitter.emit("event")).toBe(true);
|
||||
expect(myEmitter.listenerCount("event")).toBe(0);
|
||||
|
||||
expect(firstEvents).toEqual({ event: firstEvents.event }); // it shouldn't mutate
|
||||
expect(called).toBe(true);
|
||||
for (let create of waysOfCreating) {
|
||||
it(`${create.toString().slice(10, 40).replaceAll("\n", "\\n").trim()} should work`, () => {
|
||||
var myEmitter = create();
|
||||
var called = false;
|
||||
(myEmitter as EventEmitter).once("event", function () {
|
||||
called = true;
|
||||
// @ts-ignore
|
||||
expect(this).toBe(myEmitter);
|
||||
});
|
||||
}
|
||||
var firstEvents = myEmitter._events;
|
||||
expect(myEmitter.listenerCount("event")).toBe(1);
|
||||
|
||||
expect(myEmitter.emit("event")).toBe(true);
|
||||
expect(myEmitter.listenerCount("event")).toBe(0);
|
||||
|
||||
expect(firstEvents).toBe(myEmitter._events);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
}
|
||||
|
||||
test("EventEmitter.on", () => {
|
||||
var myEmitter = new EventEmitter();
|
||||
expect(myEmitter.on("foo", () => {})).toBe(myEmitter);
|
||||
});
|
||||
|
||||
test("EventEmitter.off", () => {
|
||||
var myEmitter = new EventEmitter();
|
||||
expect(myEmitter.off("foo", () => {})).toBe(myEmitter);
|
||||
});
|
||||
|
||||
// Internally, EventEmitter has a JSC::Weak with the thisValue of the listener
|
||||
test("EventEmitter GCs", async () => {
|
||||
gc();
|
||||
|
||||
const startCount = heapStats().objectTypeCounts["EventEmitter"] ?? 0;
|
||||
(function () {
|
||||
function EventEmitterSubclass(this: any) {
|
||||
EventEmitter.call(this);
|
||||
}
|
||||
|
||||
Object.setPrototypeOf(EventEmitterSubclass.prototype, EventEmitter.prototype);
|
||||
Object.setPrototypeOf(EventEmitterSubclass, EventEmitter);
|
||||
// @ts-ignore
|
||||
var myEmitter = new EventEmitterSubclass();
|
||||
myEmitter.on("foo", () => {});
|
||||
myEmitter.emit("foo");
|
||||
})();
|
||||
|
||||
await expectMaxObjectTypeCount(expect, "EventEmitter", startCount);
|
||||
});
|
||||
|
||||
18
test/js/node/events/node-builtins.test.js
Normal file
18
test/js/node/events/node-builtins.test.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { describe, it, expect } from "bun:test";
|
||||
|
||||
import { EventEmitter } from "events";
|
||||
var emitters = [EventEmitter, require("events")];
|
||||
describe("EventEmitter", () => {
|
||||
it("should emit events", () => {
|
||||
for (let Emitter of emitters) {
|
||||
const emitter = new Emitter();
|
||||
var called = false;
|
||||
const listener = () => {
|
||||
called = true;
|
||||
};
|
||||
emitter.on("test", listener);
|
||||
emitter.emit("test");
|
||||
expect(called).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user