main
js 81 lines 2.15 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import reportGlobalError from 'shared/reportGlobalError';
11
12 export default class EventEmitter<Events: Object> {
13 listenersMap: Map<string, Array<Function>> = new Map();
14
15 addListener<Event: $Keys<Events>>(
16 event: Event,
17 listener: (...Events[Event]) => mixed,
18 ): void {
19 const listeners = this.listenersMap.get(event);
20 if (listeners === undefined) {
21 this.listenersMap.set(event, [listener]);
22 } else {
23 const index = listeners.indexOf(listener);
24 if (index < 0) {
25 listeners.push(listener);
26 }
27 }
28 }
29
30 emit<Event: $Keys<Events>>(event: Event, ...args: Events[Event]): void {
31 const listeners = this.listenersMap.get(event);
32 if (listeners !== undefined) {
33 if (listeners.length === 1) {
34 // No need to clone or try/catch
35 const listener = listeners[0];
36 listener.apply(null, args);
37 } else {
38 let didThrow = false;
39 let caughtError = null;
40
41 const clonedListeners = Array.from(listeners);
42 for (let i = 0; i < clonedListeners.length; i++) {
43 const listener = clonedListeners[i];
44 try {
45 listener.apply(null, args);
46 } catch (error) {
47 if (!didThrow) {
48 didThrow = true;
49 caughtError = error;
50 } else {
51 // Continue notifying the remaining listeners, but do not hide
52 // additional failures behind the first one.
53 reportGlobalError(error);
54 }
55 }
56 }
57
58 if (didThrow) {
59 throw caughtError;
60 }
61 }
62 }
63 }
64
65 removeAllListeners(): void {
66 this.listenersMap.clear();
67 }
68
69 removeListener<Event: $Keys<Events>>(
70 event: Event,
71 listener: (...Events[Event]) => mixed,
72 ): void {
73 const listeners = this.listenersMap.get(event);
74 if (listeners !== undefined) {
75 const index = listeners.indexOf(listener);
76 if (index >= 0) {
77 listeners.splice(index, 1);
78 }
79 }
80 }
81 }