typescript-book
typescript-book copied to clipboard
Bug in TypedEvent causes listeners to not fire in specific circumstances
Hello - Thank to everyone for all your effort in putting this together. It has been incredibly useful in spinning up and learning Typescript for me recently.
I wanted to bring your attention to a bug I discovered in the TypedEvent tip.
If one of the listeners calls off on a listener that has already been run, you will (quite unexpectedly) skip event listeners. This is due to the fact that the listeners array is not being cloned before firing.
Additionally, if a listener (or a once) adds a 'once' handler during execution, it will be executed immediately and the total list will be truncated.
Both of these were very difficult issues to track down, so I was hoping to prevent anyone else the trouble.
Below is a snippet demonstrating the problem (running example that prints to console).
here is the original emit method from your tip.
public emit = (event: T) => {
/** Update any general listeners */
this.listeners.forEach((listener) => listener(event));
/** Clear the once queue */
this.listenersOncer.forEach((listener) => listener(event));
this.listenersOncer = [];
}
By configuring the listeners as follows
const otherListener = () => console.log("Hello World");
const listeners = [
() => { console.log('1'); typedEvent.off(otherListener) },
() => console.log('2'),
() => console.log('3'),
() => console.log('4'),
() => console.log('5')
];
const typedEvent = new TypedEvent();
typedEvent.on(otherListener);
listeners.forEach(l => typedEvent.on(l));
and then emitting the event
typedEvent.emit({});
We see the following console output: Hello World 1 3 4 5
Notice the missing 2
By changing the emit in the following way
public emit = (event: T) => {
// grab a copy of the listeners and once arrays so that event handlers that
// interact with this don't interfere with the current execution enumeration
const listeners : Array<(event : T) => void> = Object.assign([], this.listeners);
const oncers : Array<(event : T) => void> = Object.assign([], this.listenersOncer);
this.listenersOncer = [];
/** Update any general listeners */
listeners.forEach((listener) => listener(event));
/** Clear the once queue */
oncers.forEach((listener) => listener(event));
}
and configuring the listeners exactly the same
const typedEventWithFix = new TypedEventWithFix();
typedEventWithFix.on(otherListener);
listeners.forEach(l => typedEventWithFix.on(l));
emitting the event (typedEventWithFix.emit({});) now correctly fires all the listeners:
Hello World 1 2 3 4 5
Thanks!