Храните подписки в Map и возвращайте функцию удаления конкретной регистрации. Для ожидания асинхронных обработчиков dispatchEvent должен возвращать Promise.
Создание собственного EventEmitter‑класса
Храните подписки в Map и возвращайте функцию удаления конкретной регистрации. Для ожидания асинхронных обработчиков dispatchEvent должен возвращать Promise<void>.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Ваша задача – написать класс EventEmitter, который будет поддерживать регистрацию обработчиков событий и их последующий вызов. Нужно реализовать два публичных метода:
addEventListener(event, listener)– привязывает функцию‑обработчик к указанному событию и возвращает функцию, позволяющую отписаться от этого события.dispatchEvent(event, payload?)– запускает все обработчики, связанные с событиемevent. При вызове обработчикам может быть передан произвольный объектpayload. Обработчики могут возвращатьPromise, поэтому их выполнение должно поддерживать как синхронный, так и асинхронный сценарий.
Ниже приведён набор типовых объявлений и пример использования, который ваш класс должен поддерживать.
//
type Payload = any;
type RemoveEventListener = () => void;
type EventListener = (payload?: Payload) => void | Promise<void>;
interface IEventEmitter {
// Подписывает слушатель к событию event,
// Возвращает функцию, вызов которой отписывает слушатель от события event
addEventListener(event: string, listener: EventListener): RemoveEventListener;
// Вызывает всех слушателей, привязанных к событию event
// При вызове слушателя дополнительным аргументом может быть передан payload (произвольные дi-dispatchEvent(event: string, payload?: Payload): void;
*/
class EventEmitter {}
// Создаем инстанс EventEmitter
const button = new EventEmitter();
// Подписываем слушатели к событию 'click'
const removeHandleClick1 = button.addEventListener('click', () => console.log('called on click const removeHandleClick2 = button.addEventListener('click', () => console.log('called on click const removeHandleClick3 = button.addEventListener('click', () => console.log('click'));
// Подписываем слушатель к событию 'hover'
const removeHandleHover = button.addEventListener('hover', (payload) => console.log('called on click const removeHandleHover');
// Оповещаем всех слушателей о наступлении события 'click'
button.dispatchEvent('click'); // called on click 1, called on click 2
// Оповещаем всех слушателей о наступлении события 'hover'
button.dispatchEvent('hover', 1); // called on hover 1
// Отписываем все слушатели
removeHandleClick1();
removeHandleClick2();
removeHandleHover();
// Пытаемся снова оповестить всех слушателей о наступлении событий 'click' и 'hover'
button.dispatchEvent('click'); // Оборботчики не вызвались
button.dispatchEvent('hover'); // Оборботчики не вызвались
Ответ
Храните подписки в Map и возвращайте функцию удаления конкретной регистрации. Для ожидания асинхронных обработчиков dispatchEvent должен возвращать Promise.
В условии повреждены комментарии и строки примера. Ниже самостоятельная реализация описанного контракта; возвращаемый Promise позволяет не терять ошибки слушателей.
class EventEmitter {
events = new Map();
addEventListener(event, listener) {
if (typeof listener !== 'function') throw new TypeError('Expected listener');
const entry = { listener };
const listeners = this.events.get(event) ?? new Set();
listeners.add(entry);
this.events.set(event, listeners);
return () => {
listeners.delete(entry);
if (!listeners.size && this.events.get(event) === listeners) this.events.delete(event);
};
}
async dispatchEvent(event, payload) {
const snapshot = [...(this.events.get(event) ?? [])];
await Promise.all(snapshot.map(({ listener }) => Promise.resolve().then(() => listener(payload))));
}
}
Обработчики запускаются из снимка параллельно, изменения подписок влияют на следующую отправку. Отписка идемпотентна; одинаковую функцию можно зарегистрировать независимо дважды. Вызов используйте с await или .catch(). Для контракта строго void пришлось бы отдельно определить канал обработки асинхронных ошибок.