We use a type-safe event emitter to make sure events and their data are correct. This helps avoid mistakes when sending or listening to events.
Type-safe event emitter pattern in Typescript
interface Events {
eventName: EventDataType;
anotherEvent: AnotherDataType;
}
class TypedEventEmitter<E> {
on<K extends keyof E>(eventName: K, listener: (data: E[K]) => void): void {
// ...implementation
}
emit<K extends keyof E>(eventName: K, data: E[K]): void {
// ...implementation
}
}The Events interface defines event names and their data types.
The class uses generics to enforce correct event names and data when calling on and emit.
login event with user data, and then emit it.interface Events {
login: { user: string };
logout: void;
}
const emitter = new TypedEventEmitter<Events>();
emitter.on('login', data => {
console.log(`User logged in: ${data.user}`);
});
emitter.emit('login', { user: 'Alice' });logout event has no data, so we use void and pass undefined when emitting.emitter.on('logout', () => { console.log('User logged out'); }); emitter.emit('logout', undefined);
This program creates a type-safe event emitter with two events: message and error. It listens to both and prints messages when events happen.
interface Events {
message: { text: string };
error: { code: number; message: string };
}
class TypedEventEmitter<E> {
private listeners: { [K in keyof E]?: Array<(data: E[K]) => void> } = {};
on<K extends keyof E>(eventName: K, listener: (data: E[K]) => void): void {
if (!this.listeners[eventName]) {
this.listeners[eventName] = [];
}
this.listeners[eventName]!.push(listener);
}
emit<K extends keyof E>(eventName: K, data: E[K]): void {
this.listeners[eventName]?.forEach(listener => listener(data));
}
}
const emitter = new TypedEventEmitter<Events>();
emitter.on('message', data => {
console.log(`Message received: ${data.text}`);
});
emitter.on('error', data => {
console.log(`Error ${data.code}: ${data.message}`);
});
emitter.emit('message', { text: 'Hello, world!' });
emitter.emit('error', { code: 404, message: 'Not found' });Type safety helps catch errors before running the program.
Always define your event names and data clearly in an interface.
This pattern works well in apps with many events and listeners.
Type-safe event emitters ensure events and data match expected types.
Use interfaces to list event names and their data types.
This pattern improves code safety and clarity when working with events.
Practice
type-safe event emitter in TypeScript?Solution
Step 1: Understand type safety in event emitters
Type-safe event emitters use TypeScript types to ensure events and their data match expected types.Step 2: Identify the benefit of type safety
This prevents runtime errors by catching mismatches during development, improving code reliability.Final Answer:
It ensures events and their data match expected types, preventing runtime errors. -> Option BQuick Check:
Type safety = prevents runtime errors [OK]
- Thinking it improves runtime speed
- Assuming it logs events automatically
- Believing it allows untyped events
login (payload: string) and logout (no payload)?Solution
Step 1: Understand event payload types
Events with no payload should useundefinedto indicate no data is expected.Step 2: Check interface options
interface Events { login: string; logout: undefined; } useslogout: undefined, which correctly represents no payload for the logout event.Final Answer:
interface Events { login: string; logout: undefined; } -> Option CQuick Check:
No payload event = undefined type [OK]
- Using void which is a function return type
- Using null which means absence of value
- Using never which means impossible type
interface Events {
message: string;
error: { code: number; message: string };
}
const emitter = new TypedEmitter<Events>();
emitter.on('message', (msg) => console.log(msg));
emitter.emit('error', { code: 404, message: 'Not found' });What will be the output when the
error event is emitted?Solution
Step 1: Check registered event listeners
Only a listener for 'message' event is registered, no listener for 'error'.Step 2: Understand event emission behavior
Emitting 'error' without a listener produces no output; no automatic logging occurs.Final Answer:
No output because no listener for 'error' event is registered. -> Option AQuick Check:
Emit without listener = no output [OK]
- Assuming emit logs automatically
- Expecting runtime error without listener
- Confusing event data with console output
interface Events {
update: number;
}
const emitter = new TypedEmitter<Events>();
emitter.on('update', (data: string) => {
console.log(data);
});What is the problem with this code?
Solution
Step 1: Check event payload type in interface
The 'update' event expects anumberpayload.Step 2: Check listener callback parameter type
The callback expects astring, which mismatches the expectednumber.Final Answer:
The listener callback parameter type does not match the event payload type. -> Option DQuick Check:
Listener param type must match event payload type [OK]
- Ignoring type mismatch warnings
- Assuming any type is allowed
- Confusing event names with payload types
-
join: payload is username (string)-
leave: payload is username (string)-
message: payload is an object with user (string) and text (string)Which interface correctly defines these events for type safety?
Solution
Step 1: Analyze event payload requirements
'join' and 'leave' expect a username string; 'message' expects an object with user and text strings.Step 2: Match interface to requirements
interface ChatEvents { join: string; leave: string; message: { user: string; text: string }; } matches exactly: 'join' and 'leave' as string, 'message' as object with user and text.Final Answer:
interface ChatEvents { join: string; leave: string; message: { user: string; text: string }; } -> Option AQuick Check:
Match payload types exactly for type safety [OK]
- Using wrong payload types for events
- Omitting required object structure
- Using undefined for events that need payload
Ready to prove it?
Pass the test and this pattern moves unknown
