Skip to content

Commit 87076bd

Browse files
author
Brian Vaughn
committed
DevTools event emitter: Added more tests
Clone array before calling listeners in case listeners are added/removed during dispatch.
1 parent cffef3f commit 87076bd

File tree

2 files changed

+63
-25
lines changed

2 files changed

+63
-25
lines changed

packages/react-devtools-shared/src/__tests__/events-test.js

+30
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ describe('events', () => {
2525

2626
dispatcher.addListener('event', callback);
2727
dispatcher.addListener('event', callback);
28+
29+
dispatcher.emit('event', 123);
30+
expect(callback).toHaveBeenCalledTimes(1);
31+
expect(callback).toHaveBeenCalledWith(123);
2832
});
2933

3034
it('notifies all attached listeners of events', () => {
@@ -93,4 +97,30 @@ describe('events', () => {
9397
expect(callback2).not.toHaveBeenCalled();
9498
expect(callback3).not.toHaveBeenCalled();
9599
});
100+
101+
it('should call the initial listeners even if others are added or removed during a dispatch', () => {
102+
const callback1 = jest.fn(() => {
103+
dispatcher.removeListener('event', callback2);
104+
dispatcher.addListener('event', callback3);
105+
});
106+
const callback2 = jest.fn();
107+
const callback3 = jest.fn();
108+
109+
dispatcher.addListener('event', callback1);
110+
dispatcher.addListener('event', callback2);
111+
112+
dispatcher.emit('event', 123);
113+
expect(callback1).toHaveBeenCalledTimes(1);
114+
expect(callback1).toHaveBeenCalledWith(123);
115+
expect(callback2).toHaveBeenCalledTimes(1);
116+
expect(callback2).toHaveBeenCalledWith(123);
117+
expect(callback3).not.toHaveBeenCalled();
118+
119+
dispatcher.emit('event', 456);
120+
expect(callback1).toHaveBeenCalledTimes(2);
121+
expect(callback1).toHaveBeenCalledWith(456);
122+
expect(callback2).toHaveBeenCalledTimes(1);
123+
expect(callback3).toHaveBeenCalledTimes(1);
124+
expect(callback3).toHaveBeenCalledWith(456);
125+
});
96126
});

packages/react-devtools-shared/src/events.js

+33-25
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,21 @@
88
*/
99

1010
export default class EventEmitter<Events: Object> {
11-
listenersMap: Map<string, Set<Function>> = new Map();
11+
listenersMap: Map<string, Array<Function>> = new Map();
1212

1313
addListener<Event: $Keys<Events>>(
1414
event: Event,
1515
listener: (...$ElementType<Events, Event>) => any,
1616
): void {
1717
let listeners = this.listenersMap.get(event);
1818
if (listeners === undefined) {
19-
listeners = new Set();
20-
21-
this.listenersMap.set(event, listeners);
19+
this.listenersMap.set(event, [listener]);
20+
} else {
21+
const index = listeners.indexOf(listener);
22+
if (index < 0) {
23+
listeners.push(listener);
24+
}
2225
}
23-
24-
listeners.add(listener);
2526
}
2627

2728
emit<Event: $Keys<Events>>(
@@ -30,38 +31,45 @@ export default class EventEmitter<Events: Object> {
3031
): void {
3132
const listeners = this.listenersMap.get(event);
3233
if (listeners !== undefined) {
33-
let didThrow = false;
34-
let caughtError = null;
34+
if (listeners.length === 1) {
35+
// No need to clone or try/catch
36+
const listener = listeners[0];
37+
listener.apply(null, args);
38+
} else {
39+
let didThrow = false;
40+
let caughtError = null;
3541

36-
listeners.forEach(listener => {
37-
try {
38-
listener.apply(null, args);
39-
} catch (error) {
40-
if (caughtError === null) {
41-
didThrow = true;
42-
caughtError = error;
42+
const clonedListeners = Array.from(listeners);
43+
for (let i = 0; i < clonedListeners.length; i++) {
44+
const listener = clonedListeners[i];
45+
try {
46+
listener.apply(null, args);
47+
} catch (error) {
48+
if (caughtError === null) {
49+
didThrow = true;
50+
caughtError = error;
51+
}
4352
}
4453
}
45-
});
4654

47-
if (didThrow) {
48-
throw caughtError;
55+
if (didThrow) {
56+
throw caughtError;
57+
}
4958
}
5059
}
5160
}
5261

53-
removeAllListeners(event?: $Keys<Events>): void {
54-
if (event != null) {
55-
this.listenersMap.delete(event);
56-
} else {
57-
this.listenersMap.clear();
58-
}
62+
removeAllListeners(): void {
63+
this.listenersMap.clear();
5964
}
6065

6166
removeListener(event: $Keys<Events>, listener: Function): void {
6267
const listeners = this.listenersMap.get(event);
6368
if (listeners !== undefined) {
64-
listeners.delete(listener);
69+
const index = listeners.indexOf(listener);
70+
if (index >= 0) {
71+
listeners.splice(index, 1);
72+
}
6573
}
6674
}
6775
}

0 commit comments

Comments
 (0)