Вопрос
Реализуйте класс источника событий, который поддерживает две функции: подписку и отправку. Функция подписки принимает событие и функцию обратного вызова, которая подписывается на событие и возвращает объект подписки. Функция Emit генерирует событие с аргументами, которые будут переданы в обратные вызовы, которые подписываются на это событие. Объект подписки имеет метод Release, который отменяет подписку. В приведенном ниже коде показаны некоторые примеры использования этих функций.

const emitter = new Emitter();
const sub1 = emitter.subscribe('event 1', function(a, b) {
  console.log(a + b);
})
const sub2 = emitter.subscribe('event 2', function(a, b) {
  console.log(a - b);
})
emitter.emit('event 1', 10, 8); // output 18
emitter.emit('event 2', 10, 8); // output 2
sub1.release();
emitter.emit('event 1', 10, 8); // error!
sub2.release();
emitter.emit('event 2', 10, 8); // error!

Вопрос начинается с сопоставления одного имени события с одним обратным вызовом. Затем следуют два продолжения:

  1. Он должен поддерживать разные обратные вызовы, подписывающиеся на одно и то же событие, а выпускная часть все равно должна работать.
  2. Он должен поддерживать один и тот же обратный вызов, подписавшись на одно и то же событие несколько раз, а выпускная часть должна по-прежнему работать.

В приведенном ниже коде показано несколько примеров того, как эти функции можно использовать с последующими действиями.

const emitter = new Emitter();
const foo = function(a, b) {
  console.log(a + b);
};
const sub1 = emitter.subscribe('event 1', foo)
// A different callback subscribes to the same event
const sub2 = emitter.subscribe('event 1', function(a, b) {
  console.log(a * b);
})
// Same callback subscribes to the the same event twice
const sub3 = emitter.subscribe('event 1', foo)
emitter.emit('event 1', 10, 8); // output 18, 80, 18
sub1.release();
emitter.emit('event 1', 10, 8); // output 80, 18
sub2.release();
emitter.emit('event 1', 10, 8); // output 18

Решение

class Emitter {
  constructor() {
    this.eventMap = {};
  }

  subscribe(eventName, callback) {
    if (!this.eventMap[eventName]) {
      this.eventMap[eventName] = [];
    }

    // wrap callback in another function to save as different object
    // in order to support followup #2
    const cb = (...args) => {callback(...args)};

    this.eventMap[eventName].push(cb);

    return {
      release: () => {
        const index = this.eventMap[eventName].indexOf(cb);
        this.eventMap[eventName].splice(index, 1);

        if (!this.eventMap[eventName].length) {
          delete this.eventMap[eventName];
        }
      }
    }
  }

  emit(eventName, ...args) {
    if (!this.eventMap[eventName]) {
      throw new Error("no such event");
    }

    this.eventMap[eventName].forEach((callback) => {
      callback.call(undefined, ...args);
    });
  }
}

Компания
Мета