Выбор ответа Senior Реализация с нуля обновлено

once ломает emit в EventEmitter

Вопрос

Подписали once('x', A), затем on('x', B) и вызвали emit('x'). Что выведется при первом emit?

class Emitter {
  events = {};
  on(name, fn) { (this.events[name] ||= []).push(fn); }
  off(name, fn) {
    const list = this.events[name];
    list.splice(list.indexOf(fn), 1);
  }
  once(name, fn) {
    const wrapper = (...a) => { this.off(name, wrapper); fn(...a); };
    this.on(name, wrapper);
  }
  emit(name, ...args) {
    (this.events[name] || []).forEach((h) => h(...args));
  }
}

Попробуйте сами

Выберите вариант, и откроется разбор.

Показать ответ и разбор

Коротко

Правильный ответ: Только A: B будет пропущен. emit перебирает живой массив.

Варианты ответа

  1. A и B, в порядке подписки
  2. Только B: once снимается до вызова
  3. Только A: B будет пропущенправильный ответ
  4. Бесконечный цикл из-за splice в emit

Разбор

emit перебирает живой массив. Обёртка once во время перебора удаляет себя через splice, B сдвигается на индекс 0, а forEach переходит к индексу 1, где уже ничего нет. B пропускается при этом emit и сработает только при следующем. Исправление: перебирать копию, [...list].forEach(...). Ещё у этого off есть баг: если функции нет в списке, indexOf вернёт -1, и splice(-1, 1) удалит последний обработчик.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.