EventEmitter

Why?

Nothing special, but notice the implementation of emit. Previously I ran into a pitfall when doing this:

const e = new EventEmitter();
e.addListener(() => {
    console.log('#1');
});
const callbackRunOnce = () => {
    console.log('#2');
    e.removeListener(callbackRunOnce);
};
e.addListener(callbackRunOnce);
e.addListener(() => {
    console.log('#3');
});
e.emit();

When the e emits, the second callback did not run. What happened exactly? Let's check the implementation of a common EventEmitter

class EventEmitter {
    private readonly callbacks: Callback[] = [];

    addListener(callback: Callback) {
        this.callbacks.push(callback);
    }

    removeListener(callback: Callback) {
        const index = this.callbacks.indexOf(callback);
        if (index === -1) return;
        this.callbacks.splice(index, 1);
    }

    emit() {
        // Implementation #1: old-fashioned for loop
        for (let i = 0; i < this.callbacks.length; i++) {
            const callback = this.callbacks[i];
            callback();
        }
        // Implementation #2: modern array method
        this.callbacks.forEach(callback => callback());
    }
}

The result of Implementation #1:

#1
#2

The result of Implementation #2:

#1
#2

When looping over the callbacks array, the second callback itself calls removeListener method to remove itself from the array, but the index of for-loop or forEach incremented from 1 to 2, it'll targets to nothing because now the callbacks array only have 2 callbacks!

In general, modifying an array when looping over it is very likely to cause unexpected result. If you gonna do that, I think while loop is a safer choice because you have to control the index yourself.

emit() {
    let index = 0;
    while (index < this.callbacks.length) {
        const callback = this.callbacks[index];
        callback();
        // if the callback is still in the array, increment the index
        if (this.callbacks[index] === callback) {
            index++;
        }
    }
}