Consider this TypeScript code using a type-safe event emitter pattern. What will be printed when the code runs?
type Events = {
login: (user: string) => void;
logout: () => void;
};
class EventEmitter<E> {
private listeners: { [K in keyof E]?: E[K][] } = {};
on<K extends keyof E>(eventName: K, listener: E[K]) {
if (!this.listeners[eventName]) {
this.listeners[eventName] = [];
}
this.listeners[eventName]!.push(listener);
}
emit<K extends keyof E>(eventName: K, ...args: Parameters<E[K]>) {
this.listeners[eventName]?.forEach(listener => {
(listener as (...args: any[]) => void)(...args);
});
}
}
const emitter = new EventEmitter<Events>();
emitter.on('login', user => console.log(`User logged in: ${user}`));
emitter.on('logout', () => console.log('User logged out'));
emitter.emit('login', 'Alice');
emitter.emit('logout');Check how many listeners are registered and what events are emitted.
The emitter registers two listeners: one for 'login' and one for 'logout'. When 'login' is emitted with 'Alice', it prints "User logged in: Alice". When 'logout' is emitted, it prints "User logged out".
In a type-safe event emitter pattern, which TypeScript feature is primarily responsible for ensuring that event names and their listener argument types match correctly?
Think about how the event names and listener types are linked in the class definition.
Generics combined with mapped types allow the event emitter to enforce that only valid event names can be used and that listeners have the correct argument types for each event.
Identify the error produced by this TypeScript code snippet:
type Events = {
data: (value: number) => void;
};
class EventEmitter {
private listeners: { [K in keyof E]?: E[K][] } = {};
on(eventName: K, listener: E[K]) {
this.listeners[eventName].push(listener);
}
emit(eventName: K, ...args: Parameters) {
this.listeners[eventName]?.forEach(listener => listener(...args));
}
} Check what happens if no listeners are registered yet.
The listeners object starts empty. Accessing this.listeners[eventName] returns undefined initially, so calling push on undefined causes a runtime TypeError.
Choose the correct TypeScript signature for the emit method to ensure type safety for event arguments.
Look for the option that uses generics and extracts parameter types from the listener function.
Option A uses a generic constrained to the keys of E and uses Parameters to get the argument types of the listener, ensuring type safety.
Given this code snippet, how many listeners are registered for the 'update' event after execution?
type Events = {
update: (data: string) => void;
};
class EventEmitter {
private listeners: { [K in keyof E]?: E[K][] } = {};
on(eventName: K, listener: E[K]) {
if (!this.listeners[eventName]) {
this.listeners[eventName] = [];
}
this.listeners[eventName]!.push(listener);
}
off(eventName: K, listener: E[K]) {
this.listeners[eventName] = this.listeners[eventName]?.filter(l => l !== listener);
}
}
const emitter = new EventEmitter();
function listener1(data: string) { console.log('Listener 1:', data); }
function listener2(data: string) { console.log('Listener 2:', data); }
emitter.on('update', listener1);
emitter.on('update', listener2);
emitter.off('update', listener1);
emitter.on('update', listener1); Count how many times listeners are added and removed.
Initially, listener1 and listener2 are added (2 listeners). Then listener1 is removed (1 listener left). Then listener1 is added again (2 listeners total).