O

Observer Pattern

The Observer pattern defines a subscription mechanism to notify objects about events.

Code

typescript
1// Event types
2type EventMap = {
3 userCreated: { userId: number; email: string };
4 userDeleted: { userId: number };
5 orderPlaced: { orderId: string; total: number };
6};
7
8// Generic typed event emitter
9class TypedEventEmitter<T extends Record<string, unknown>> {
10 private listeners: {
11 [K in keyof T]?: Array<(data: T[K]) => void>;
12 } = {};
13
14 on<K extends keyof T>(event: K, callback: (data: T[K]) => void): () => void {
15 if (!this.listeners[event]) {
16 this.listeners[event] = [];
17 }
18 this.listeners[event]!.push(callback);
19
20 // Return unsubscribe function
21 return () => this.off(event, callback);
22 }
23
24 off<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {
25 const callbacks = this.listeners[event];
26 if (callbacks) {
27 const index = callbacks.indexOf(callback);
28 if (index > -1) {
29 callbacks.splice(index, 1);
30 }
31 }
32 }
33
34 emit<K extends keyof T>(event: K, data: T[K]): void {
35 const callbacks = this.listeners[event];
36 if (callbacks) {
37 callbacks.forEach((callback) => callback(data));
38 }
39 }
40
41 once<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {
42 const unsubscribe = this.on(event, (data) => {
43 unsubscribe();
44 callback(data);
45 });
46 }
47}
48
49// Usage with application events
50const events = new TypedEventEmitter<EventMap>();
51
52// Subscribe to events
53events.on("userCreated", ({ userId, email }) => {
54 console.log(`New user: ${email} (ID: ${userId})`);
55});
56
57events.on("userCreated", ({ email }) => {
58 console.log(`Sending welcome email to ${email}`);
59});
60
61const unsubscribeOrder = events.on("orderPlaced", ({ orderId, total }) => {
62 console.log(`Order ${orderId}: $${total.toFixed(2)}`);
63});
64
65// One-time subscription
66events.once("userDeleted", ({ userId }) => {
67 console.log(`User ${userId} deleted (one-time notification)`);
68});
69
70// Emit events
71events.emit("userCreated", { userId: 1, email: "alice@example.com" });
72events.emit("orderPlaced", { orderId: "ORD-001", total: 99.99 });
73events.emit("userDeleted", { userId: 1 });
74events.emit("userDeleted", { userId: 2 }); // Won't trigger once handler
75
76// Unsubscribe from order events
77unsubscribeOrder();
78events.emit("orderPlaced", { orderId: "ORD-002", total: 149.99 }); // Not logged

Run this example locally

$ npx ts-node patterns/observer.ts