Skip to content

Commit

Permalink
feat(node): add EventEmitter.errorMonitor (denoland/deno#3960)
Browse files Browse the repository at this point in the history
  • Loading branch information
cknight authored Feb 11, 2020
1 parent c7209b3 commit eb3632e
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 11 deletions.
10 changes: 10 additions & 0 deletions node/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface WrappedFunction extends Function {
*/
export default class EventEmitter {
public static defaultMaxListeners = 10;
public static errorMonitor = Symbol("events.errorMonitor");
private maxListeners: number | undefined;
private _events: Map<string | symbol, Array<Function | WrappedFunction>>;

Expand Down Expand Up @@ -88,6 +89,12 @@ export default class EventEmitter {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public emit(eventName: string | symbol, ...args: any[]): boolean {
if (this._events.has(eventName)) {
if (
eventName === "error" &&
this._events.get(EventEmitter.errorMonitor)
) {
this.emit(EventEmitter.errorMonitor, ...args);
}
const listeners = (this._events.get(eventName) as Function[]).slice(); // We copy with slice() so array is not mutated during emit
for (const listener of listeners) {
try {
Expand All @@ -98,6 +105,9 @@ export default class EventEmitter {
}
return true;
} else if (eventName === "error") {
if (this._events.get(EventEmitter.errorMonitor)) {
this.emit(EventEmitter.errorMonitor, ...args);
}
const errMsg = args.length > 0 ? args[0] : Error("Unhandled error.");
throw errMsg;
}
Expand Down
60 changes: 49 additions & 11 deletions node/events_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ test({
name:
'When adding a new event, "eventListener" event is fired before adding the listener',
fn() {
let eventsFired = [];
let eventsFired: string[] = [];
const testEmitter = new EventEmitter();
testEmitter.on("newListener", event => {
if (event !== "newListener") {
Expand All @@ -36,7 +36,7 @@ test({
name:
'When removing a listenert, "removeListener" event is fired after removal',
fn() {
const eventsFired = [];
const eventsFired: string[] = [];
const testEmitter = new EventEmitter();
testEmitter.on("removeListener", () => {
eventsFired.push("removeListener");
Expand Down Expand Up @@ -80,7 +80,7 @@ test({
name: "Emitted events are called synchronously in the order they were added",
fn() {
const testEmitter = new EventEmitter();
const eventsFired = [];
const eventsFired: string[] = [];
testEmitter.on("event", oneArg => {
eventsFired.push("event(" + oneArg + ")");
});
Expand Down Expand Up @@ -162,7 +162,7 @@ test({
test({
name: "Events can be registered to only fire once",
fn() {
let eventsFired = [];
let eventsFired: string[] = [];
const testEmitter = new EventEmitter();
//prove multiple emits on same event first (when registered with 'on')
testEmitter.on("multiple event", () => {
Expand All @@ -187,7 +187,7 @@ test({
name:
"You can inject a listener into the start of the stack, rather than at the end",
fn() {
const eventsFired = [];
const eventsFired: string[] = [];
const testEmitter = new EventEmitter();
testEmitter.on("event", () => {
eventsFired.push("first");
Expand All @@ -206,7 +206,7 @@ test({
test({
name: 'You can prepend a "once" listener',
fn() {
const eventsFired = [];
const eventsFired: string[] = [];
const testEmitter = new EventEmitter();
testEmitter.on("event", () => {
eventsFired.push("first");
Expand Down Expand Up @@ -288,7 +288,7 @@ test({
name: "all listeners complete execution even if removed before execution",
fn() {
const testEmitter = new EventEmitter();
let eventsProcessed = [];
let eventsProcessed: string[] = [];
const listenerB = (): number => eventsProcessed.push("B");
const listenerA = (): void => {
eventsProcessed.push("A");
Expand All @@ -311,7 +311,7 @@ test({
name: 'Raw listener will return event listener or wrapped "once" function',
fn() {
const testEmitter = new EventEmitter();
const eventsProcessed = [];
const eventsProcessed: string[] = [];
const listenerA = (): number => eventsProcessed.push("A");
const listenerB = (): number => eventsProcessed.push("B");
testEmitter.on("event", listenerA);
Expand All @@ -335,7 +335,7 @@ test({
"Once wrapped raw listeners may be executed multiple times, until the wrapper is executed",
fn() {
const testEmitter = new EventEmitter();
let eventsProcessed = [];
let eventsProcessed: string[] = [];
const listenerA = (): number => eventsProcessed.push("A");
testEmitter.once("once-event", listenerA);

Expand All @@ -356,7 +356,7 @@ test({
test({
name: "Can add once event listener to EventEmitter via standalone function",
async fn() {
const ee: EventEmitter = new EventEmitter();
const ee = new EventEmitter();
setTimeout(() => {
ee.emit("event", 42, "foo");
}, 0);
Expand All @@ -383,7 +383,7 @@ test({
test({
name: "Only valid integers are allowed for max listeners",
fn() {
const ee: EventEmitter = new EventEmitter();
const ee = new EventEmitter();
ee.setMaxListeners(0);
assertThrows(
() => {
Expand All @@ -401,3 +401,41 @@ test({
);
}
});

test({
name: "ErrorMonitor can spy on error events without consuming them",
fn() {
const ee = new EventEmitter();
let events: string[] = [];
//unhandled error scenario should throw
assertThrows(
() => {
ee.emit("error");
},
Error,
"Unhandled error"
);

ee.on(EventEmitter.errorMonitor, () => {
events.push("errorMonitor event");
});

//error is still unhandled but also intercepted by error monitor
assertThrows(
() => {
ee.emit("error");
},
Error,
"Unhandled error"
);
assertEquals(events, ["errorMonitor event"]);

//A registered error handler won't throw, but still be monitored
events = [];
ee.on("error", () => {
events.push("error");
});
ee.emit("error");
assertEquals(events, ["errorMonitor event", "error"]);
}
});

0 comments on commit eb3632e

Please sign in to comment.