Skip to main content

EventEmitter

// Use set when we want unique set of listeners for eventName
// Use array when we want to allow duplicate listeners for eventName

export default class EventEmitter {
constructor() {
this.subscribeEvents = new Map();
}

on(eventName, listener) {
if (!this.subscribeEvents.has(eventName)) {
this.subscribeEvents.set(eventName, []);
}

this.subscribeEvents.get(eventName).push(listener);

return this;
}

off(eventName, listener) {
const listeners = this.subscribeEvents.get(eventName);

if (!listeners) {
return this;
}

// Remove only one occurrence
const index = listeners.indexOf(listener);

if (index !== -1) {
listeners.splice(index, 1);
}

if (listeners.length === 0) {
this.subscribeEvents.delete(eventName);
}

return this;
}

emit(eventName, ...args) {
const listeners = this.subscribeEvents.get(eventName);

if (!listeners || listeners.length === 0) {
return false;
}

// clone array in case listeners mutate during emit
[...listeners].forEach((listener) => {
listener.apply(this, args);
});

return true;
}
}

/**
import EventEmitter from './event-emitter';

describe('EventEmitter', () => {
test('constructor', () => {
const emitter = new EventEmitter();
expect(emitter).toBeInstanceOf(EventEmitter);
});

describe('methods can be chained', () => {
test('on() can be chained', () => {
const emitter = new EventEmitter();
emitter.on('foo', () => {}).on('foo', () => {});
});

test('off() can be chained', () => {
const emitter = new EventEmitter();
emitter.off('foo', () => {}).off('foo', () => {});
});
});

describe('subscribe', () => {
test('single listener', () => {
const emitter = new EventEmitter();
let a = 0;
emitter.on('foo', () => {
a = 1;
});
emitter.emit('foo');

expect(a).toBe(1);
});

test('multiple listeners', () => {
const emitter = new EventEmitter();
let a = 0,
b = 1;
emitter.on('foo', () => {
a = 1;
});
emitter.on('foo', () => {
b = 3;
});
emitter.emit('foo');

expect(a).toBe(1);
expect(b).toBe(3);
});

test('multiple events', () => {
const emitter = new EventEmitter();
let a = 0,
b = 1;
emitter.on('foo', () => {
a = 1;
});
emitter.on('bar', () => {
b = 3;
});
emitter.emit('foo');
expect(a).toBe(1);
expect(b).toBe(1);

emitter.emit('bar');
expect(b).toBe(3);
});

test('same listener added multiple times', () => {
const emitter = new EventEmitter();

let num = 1;
function double() {
num *= 2;
}

emitter.on('double', double);
emitter.emit('double');
expect(num).toBe(2);

emitter.on('double', double);
emitter.emit('double');
expect(num).toBe(8);
});
});

describe('emit', () => {
test('existing event returns true', () => {
const emitter = new EventEmitter();

emitter.on('foo', () => {});
expect(emitter.emit('foo')).toBe(true);
});

describe('listeners are invoked with arguments', () => {
test('single argument', () => {
const emitter = new EventEmitter();

let sum = 0;
emitter.on('foo', (a: number) => {
sum = a;
});
emitter.emit('foo', 3);
expect(sum).toBe(3);

emitter.emit('foo', 5);
expect(sum).toBe(5);
});

test('two arguments', () => {
const emitter = new EventEmitter();

let sum = 0;
emitter.on('foo', (a: number, b: number) => {
sum = a + b;
});
emitter.emit('foo', 3, 5);
expect(sum).toBe(8);

emitter.emit('foo', 4, 13);
expect(sum).toBe(17);
});

test('three arguments', () => {
const emitter = new EventEmitter();

let product = 0;
emitter.on('foo', (a: number, b: number, c: number) => {
product = a * b * c;
});
emitter.emit('foo', 3, 5, 6);
expect(product).toBe(90);

emitter.emit('foo', 4, 13, 9);
expect(product).toBe(468);
});
});

describe('non-existing event name returns false', () => {
test('custom event', () => {
const emitter = new EventEmitter();

expect(emitter.emit('foo')).toBe(false);
});

test('same name as built-in event', () => {
const emitter = new EventEmitter();

expect(emitter.emit('toString')).toBe(false);
});
});
});

describe('unsubscribe', () => {
test('single listener', () => {
const emitter = new EventEmitter();

let sum = 0;
function addTwoNumbers(a: number, b: number) {
sum = a + b;
}
emitter.on('foo', addTwoNumbers);
expect(emitter.emit('foo', 2, 5)).toBe(true);
expect(sum).toBe(7);

emitter.off('foo', addTwoNumbers);
expect(emitter.emit('foo', -3, 9)).toBe(false);
expect(sum).toBe(7);
});

test('multiple listeners', () => {
const emitter = new EventEmitter();

let sum = 0;
function addTwoNumbers(a: number, b: number) {
sum = a + b;
}
emitter.on('foo', addTwoNumbers);
expect(emitter.emit('foo', 2, 5)).toBe(true);
expect(sum).toBe(7);

let product = 0;
function multiplyTwoNumbers(a: number, b: number) {
product = a * b;
}
emitter.on('foo', multiplyTwoNumbers);
expect(emitter.emit('foo', 4, 5)).toBe(true);
expect(sum).toBe(9);
expect(product).toBe(20);

emitter.off('foo', addTwoNumbers);
expect(emitter.emit('foo', -3, 9)).toBe(true);
expect(sum).toBe(9);
expect(product).toBe(-27);

emitter.off('foo', multiplyTwoNumbers);
expect(emitter.emit('foo', 3, 7)).toBe(false);
expect(sum).toBe(9);
expect(product).toBe(-27);
});

test('multiple events', () => {
const emitter = new EventEmitter();

let sum = 0;
function addTwoNumbers(a: number, b: number) {
sum = a + b;
}
emitter.on('foo', addTwoNumbers);
expect(emitter.emit('foo', 2, 5)).toBe(true);
expect(sum).toBe(7);

expect(emitter.emit('bar', 3, 7)).toBe(false);
emitter.on('bar', addTwoNumbers);
expect(emitter.emit('bar', 3, 7)).toBe(true);
expect(sum).toBe(10);

emitter.off('foo', addTwoNumbers);
expect(emitter.emit('foo', -3, 9)).toBe(false);
expect(sum).toBe(10);

emitter.off('bar', addTwoNumbers);
expect(emitter.emit('bar', -3, 9)).toBe(false);
expect(sum).toBe(10);
});

test('same listener added multiple times removed correctly', () => {
const emitter = new EventEmitter();

let num = 1;
function double() {
num *= 2;
}

emitter.on('double', double);
emitter.emit('double');
expect(num).toBe(2);

emitter.on('double', double);
emitter.emit('double');
expect(num).toBe(8);

emitter.off('double', double);
emitter.emit('double');
expect(num).toBe(16);

emitter.off('double', double);
emitter.emit('double');
expect(num).toBe(16);
});
});
});

*/