Skip to content

Commit b0d2817

Browse files
authored
feat(emitter): safe event emitter
1 parent 1c2537e commit b0d2817

File tree

1 file changed

+84
-0
lines changed

1 file changed

+84
-0
lines changed

src/events/index.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,87 @@ export class EventEmitterWrapper implements EventEmitter {
8585
return this._wrapped.listenerCount(type);
8686
}
8787
}
88+
89+
90+
type Handler = (...args: any[]) => void;
91+
interface EventMap {
92+
[k: string]: Handler | Handler[] | undefined;
93+
}
94+
95+
/** @function safeApply */
96+
function safeApply<T, A extends any[]> (handler: (this: T, ...args: A) => void, context: T, args: A): void {
97+
try {
98+
Reflect.apply(handler, context, args);
99+
} catch (err) {
100+
// Throw error after timeout so as not to interrupt the stack
101+
setTimeout(() => {
102+
throw err;
103+
});
104+
}
105+
}
106+
107+
/** @function arrayClone */
108+
function arrayClone<T> (arr: T[]): T[] {
109+
const n = arr.length;
110+
const copy = new Array(n);
111+
for (let i = 0; i < n; i += 1) {
112+
copy[i] = arr[i];
113+
}
114+
return copy;
115+
}
116+
117+
/**
118+
* SafeEventEmitter
119+
* @license ISC License
120+
* Copyright (c) 2020 MetaMask
121+
*
122+
*/
123+
124+
/** @class SafeEventEmitter */
125+
export default class SafeEventEmitter extends EventEmitter {
126+
emit (type: string, ...args: any[]): boolean {
127+
let doError = type === 'error';
128+
129+
const events: EventMap = (this as any)._events;
130+
if (events !== undefined) {
131+
doError = doError && events.error === undefined;
132+
} else if (!doError) {
133+
return false;
134+
}
135+
136+
// If there is no 'error' event listener then throw.
137+
if (doError) {
138+
let er;
139+
if (args.length > 0) {
140+
[er] = args;
141+
}
142+
if (er instanceof Error) {
143+
// Note: The comments on the `throw` lines are intentional, they show
144+
// up in Node's output if this results in an unhandled exception.
145+
throw er; // Unhandled 'error' event
146+
}
147+
// At least give some kind of context to the user
148+
const err = new Error(`Unhandled error.${er ? ` (${er.message})` : ''}`);
149+
(err as any).context = er;
150+
throw err; // Unhandled 'error' event
151+
}
152+
153+
const handler = events[type];
154+
155+
if (handler === undefined) {
156+
return false;
157+
}
158+
159+
if (typeof handler === 'function') {
160+
safeApply(handler, this, args);
161+
} else {
162+
const len = handler.length;
163+
const listeners = arrayClone(handler);
164+
for (let i = 0; i < len; i += 1) {
165+
safeApply(listeners[i], this, args);
166+
}
167+
}
168+
169+
return true;
170+
}
171+
}

0 commit comments

Comments
 (0)