0
# Event System
1
2
The Web3 event system provides robust event emission and handling capabilities with type-safe event listeners, Promise-Event hybrids, and comprehensive lifecycle management. It serves as the foundation for all asynchronous communication within the Web3 ecosystem.
3
4
## Capabilities
5
6
### Web3EventEmitter Class
7
8
Core event emitter implementation with type-safe event handling and listener management.
9
10
```typescript { .api }
11
/**
12
* Web3 event emitter with type-safe event handling and listener management
13
* @template T - Event map type defining available events and their data types
14
*/
15
class Web3EventEmitter<T extends Web3EventMap = Web3EventMap> implements Web3Emitter<T> {
16
constructor();
17
18
// Event listener management
19
on<K extends Web3EventKey<T>>(eventName: K, fn: Web3EventCallback<T[K]>): void;
20
once<K extends Web3EventKey<T>>(eventName: K, fn: Web3EventCallback<T[K]>): void;
21
off<K extends Web3EventKey<T>>(eventName: K, fn: Web3EventCallback<T[K]>): void;
22
23
// Event emission
24
emit<K extends Web3EventKey<T>>(eventName: K, params: T[K]): void;
25
26
// Listener inspection
27
listenerCount<K extends Web3EventKey<T>>(eventName: K): number;
28
listeners<K extends Web3EventKey<T>>(eventName: K): Function[];
29
eventNames(): (string | symbol)[];
30
31
// Management
32
removeAllListeners(): EventEmitter;
33
setMaxListenerWarningThreshold(maxListenersWarningThreshold: number): void;
34
getMaxListeners(): number;
35
}
36
```
37
38
**Usage Examples:**
39
40
```typescript
41
import { Web3EventEmitter } from "web3-core";
42
43
// Define event map for type safety
44
interface MyEvents {
45
dataReceived: { data: string; timestamp: number };
46
error: { message: string; code: number };
47
connected: { address: string };
48
disconnected: undefined;
49
}
50
51
// Create typed event emitter
52
const emitter = new Web3EventEmitter<MyEvents>();
53
54
// Add event listeners with type safety
55
emitter.on("dataReceived", ({ data, timestamp }) => {
56
console.log(`Received data: ${data} at ${new Date(timestamp)}`);
57
});
58
59
emitter.on("error", ({ message, code }) => {
60
console.error(`Error ${code}: ${message}`);
61
});
62
63
emitter.on("connected", ({ address }) => {
64
console.log(`Connected to ${address}`);
65
});
66
67
emitter.on("disconnected", () => {
68
console.log("Disconnected");
69
});
70
71
// Emit events
72
emitter.emit("dataReceived", { data: "Hello World", timestamp: Date.now() });
73
emitter.emit("error", { message: "Connection failed", code: 500 });
74
emitter.emit("connected", { address: "127.0.0.1:8080" });
75
emitter.emit("disconnected", undefined);
76
77
// One-time listener
78
emitter.once("connected", ({ address }) => {
79
console.log(`First connection to ${address}`);
80
});
81
82
// Remove specific listener
83
const errorHandler = ({ message, code }: { message: string; code: number }) => {
84
console.error(`Error: ${message}`);
85
};
86
emitter.on("error", errorHandler);
87
emitter.off("error", errorHandler);
88
89
// Inspect listeners
90
console.log("Error listeners:", emitter.listenerCount("error"));
91
console.log("All event names:", emitter.eventNames());
92
```
93
94
### Web3PromiEvent Class
95
96
Promise-Event hybrid that combines Promise functionality with event emission for long-running operations.
97
98
```typescript { .api }
99
/**
100
* Promise-like object that also emits events during execution
101
* @template ResolveType - Type of the final resolved value
102
* @template EventMap - Event map for intermediate events during execution
103
*/
104
class Web3PromiEvent<ResolveType, EventMap extends Web3EventMap = Web3EventMap>
105
extends Web3EventEmitter<EventMap>
106
implements Promise<ResolveType> {
107
108
constructor(executor: PromiseExecutor<ResolveType>);
109
110
// Promise interface
111
then<TResult1 = ResolveType, TResult2 = never>(
112
onfulfilled?: ((value: ResolveType) => TResult1 | PromiseLike<TResult1>) | null,
113
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
114
): Promise<TResult1 | TResult2>;
115
116
catch<TResult = never>(
117
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
118
): Promise<ResolveType | TResult>;
119
120
finally(onfinally?: (() => void) | null): Promise<ResolveType>;
121
122
// Enhanced event methods that return this for chaining
123
on<K extends Web3EventKey<EventMap>>(eventName: K, fn: Web3EventCallback<EventMap[K]>): this;
124
once<K extends Web3EventKey<EventMap>>(eventName: K, fn: Web3EventCallback<EventMap[K]>): this;
125
126
// Promise symbol
127
readonly [Symbol.toStringTag]: 'Promise';
128
}
129
130
/**
131
* Promise executor function type
132
* @template T - Type of resolved value
133
*/
134
type PromiseExecutor<T> = (
135
resolve: (value: T) => void,
136
reject: (reason: unknown) => void
137
) => void;
138
```
139
140
**Usage Examples:**
141
142
```typescript
143
import { Web3PromiEvent } from "web3-core";
144
145
// Define event map for transaction process
146
interface TransactionEvents {
147
sending: { txHash: string };
148
sent: { txHash: string };
149
transactionHash: { txHash: string };
150
confirmation: { confirmationNumber: number; receipt: any };
151
}
152
153
// Create PromiEvent for transaction sending
154
function sendTransaction(txData: any): Web3PromiEvent<any, TransactionEvents> {
155
return new Web3PromiEvent((resolve, reject) => {
156
// Simulate transaction process
157
setTimeout(() => {
158
const txHash = "0x1234567890abcdef...";
159
160
// Emit sending event
161
promiEvent.emit("sending", { txHash });
162
163
setTimeout(() => {
164
// Emit sent event
165
promiEvent.emit("sent", { txHash });
166
promiEvent.emit("transactionHash", { txHash });
167
168
// Simulate confirmations
169
let confirmationCount = 0;
170
const confirmationInterval = setInterval(() => {
171
confirmationCount++;
172
promiEvent.emit("confirmation", {
173
confirmationNumber: confirmationCount,
174
receipt: { txHash, status: 1, blockNumber: 18000000 + confirmationCount }
175
});
176
177
if (confirmationCount >= 3) {
178
clearInterval(confirmationInterval);
179
resolve({ txHash, confirmations: confirmationCount });
180
}
181
}, 1000);
182
}, 2000);
183
}, 1000);
184
});
185
}
186
187
// Use PromiEvent - can be used as both Promise and EventEmitter
188
const txPromiEvent = sendTransaction({ to: "0x742d35Cc6634C0532925a3b8D0d3", value: "1000000000000000000" });
189
190
// Listen to events during execution
191
txPromiEvent
192
.on("sending", ({ txHash }) => {
193
console.log("Transaction sending:", txHash);
194
})
195
.on("sent", ({ txHash }) => {
196
console.log("Transaction sent:", txHash);
197
})
198
.on("transactionHash", ({ txHash }) => {
199
console.log("Transaction hash received:", txHash);
200
})
201
.on("confirmation", ({ confirmationNumber, receipt }) => {
202
console.log(`Confirmation ${confirmationNumber}:`, receipt.blockNumber);
203
});
204
205
// Use as Promise
206
txPromiEvent
207
.then((result) => {
208
console.log("Transaction completed:", result);
209
})
210
.catch((error) => {
211
console.error("Transaction failed:", error);
212
});
213
214
// Or with async/await
215
try {
216
const result = await txPromiEvent;
217
console.log("Transaction successful:", result.txHash);
218
} catch (error) {
219
console.error("Transaction error:", error);
220
}
221
```
222
223
### Event Type System
224
225
Type definitions for creating type-safe event maps and handlers.
226
227
```typescript { .api }
228
/**
229
* Base event map type - maps event names to their data types
230
*/
231
type Web3EventMap = Record<string, unknown>;
232
233
/**
234
* Event key type - ensures key exists in event map
235
* @template T - Event map type
236
*/
237
type Web3EventKey<T extends Web3EventMap> = string & keyof T;
238
239
/**
240
* Event callback function type
241
* @template T - Event data type
242
*/
243
type Web3EventCallback<T> = (params: T) => void | Promise<void>;
244
245
/**
246
* Event emitter interface definition
247
* @template T - Event map type
248
*/
249
interface Web3Emitter<T extends Web3EventMap> {
250
on<K extends Web3EventKey<T>>(eventName: K, fn: Web3EventCallback<T[K]>): void;
251
once<K extends Web3EventKey<T>>(eventName: K, fn: Web3EventCallback<T[K]>): void;
252
off<K extends Web3EventKey<T>>(eventName: K, fn: Web3EventCallback<T[K]>): void;
253
emit<K extends Web3EventKey<T>>(eventName: K, params: T[K]): void;
254
}
255
```
256
257
**Usage Examples:**
258
259
```typescript
260
// Define complex event map
261
interface BlockchainEvents {
262
blockReceived: {
263
blockNumber: number;
264
blockHash: string;
265
timestamp: number;
266
transactions: string[]
267
};
268
transactionPending: {
269
txHash: string;
270
from: string;
271
to: string;
272
value: string
273
};
274
error: {
275
code: number;
276
message: string;
277
details?: any
278
};
279
connected: {
280
provider: string;
281
networkId: number
282
};
283
disconnected: undefined;
284
}
285
286
// Create typed emitter
287
class BlockchainMonitor extends Web3EventEmitter<BlockchainEvents> {
288
constructor() {
289
super();
290
this.setMaxListenerWarningThreshold(50); // Higher threshold for blockchain events
291
}
292
293
startMonitoring() {
294
// Simulate blockchain monitoring
295
setInterval(() => {
296
this.emit("blockReceived", {
297
blockNumber: Math.floor(Math.random() * 1000000),
298
blockHash: `0x${Math.random().toString(16).substr(2, 64)}`,
299
timestamp: Date.now(),
300
transactions: [`0x${Math.random().toString(16).substr(2, 64)}`]
301
});
302
}, 12000); // Every 12 seconds
303
}
304
305
reportError(code: number, message: string, details?: any) {
306
this.emit("error", { code, message, details });
307
}
308
}
309
310
// Use typed monitor
311
const monitor = new BlockchainMonitor();
312
313
monitor.on("blockReceived", ({ blockNumber, blockHash, timestamp, transactions }) => {
314
console.log(`Block ${blockNumber} (${blockHash}) received at ${new Date(timestamp)}`);
315
console.log(`Contains ${transactions.length} transactions`);
316
});
317
318
monitor.on("error", ({ code, message, details }) => {
319
console.error(`Blockchain error ${code}: ${message}`, details);
320
});
321
322
monitor.startMonitoring();
323
```
324
325
### Advanced Event Patterns
326
327
Common patterns and best practices for event-driven Web3 applications.
328
329
```typescript { .api }
330
/**
331
* Event listener management utilities
332
*/
333
listenerCount<K extends Web3EventKey<T>>(eventName: K): number;
334
listeners<K extends Web3EventKey<T>>(eventName: K): Function[];
335
eventNames(): (string | symbol)[];
336
removeAllListeners(): EventEmitter;
337
setMaxListenerWarningThreshold(maxListenersWarningThreshold: number): void;
338
getMaxListeners(): number;
339
```
340
341
**Usage Examples:**
342
343
```typescript
344
// Pattern: Event debouncing
345
class DebouncedEmitter extends Web3EventEmitter<{ dataChanged: string }> {
346
private debounceTimer?: NodeJS.Timeout;
347
348
emitDebounced(data: string) {
349
if (this.debounceTimer) {
350
clearTimeout(this.debounceTimer);
351
}
352
353
this.debounceTimer = setTimeout(() => {
354
this.emit("dataChanged", data);
355
}, 300); // 300ms debounce
356
}
357
}
358
359
// Pattern: Event aggregation
360
class EventAggregator extends Web3EventEmitter<{ batch: string[] }> {
361
private buffer: string[] = [];
362
private batchTimer?: NodeJS.Timeout;
363
364
add(item: string) {
365
this.buffer.push(item);
366
367
if (!this.batchTimer) {
368
this.batchTimer = setTimeout(() => {
369
this.emit("batch", [...this.buffer]);
370
this.buffer = [];
371
this.batchTimer = undefined;
372
}, 1000); // Batch every second
373
}
374
}
375
}
376
377
// Pattern: Error handling with retry
378
class ResilientEmitter extends Web3EventEmitter<{
379
data: any;
380
error: Error;
381
retry: { attempt: number; maxAttempts: number }
382
}> {
383
async emitWithRetry(data: any, maxAttempts = 3) {
384
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
385
try {
386
this.emit("data", data);
387
return; // Success
388
} catch (error) {
389
this.emit("retry", { attempt, maxAttempts });
390
391
if (attempt === maxAttempts) {
392
this.emit("error", error as Error);
393
throw error;
394
}
395
396
// Wait before retry
397
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
398
}
399
}
400
}
401
}
402
403
// Pattern: Event metrics and monitoring
404
class MonitoredEmitter extends Web3EventEmitter<{ data: any; metrics: any }> {
405
private eventCounts = new Map<string, number>();
406
private startTime = Date.now();
407
408
emit<K extends string>(eventName: K, params: any): void {
409
// Track event metrics
410
const currentCount = this.eventCounts.get(eventName) || 0;
411
this.eventCounts.set(eventName, currentCount + 1);
412
413
// Emit metrics periodically
414
if (currentCount % 100 === 0) {
415
this.emit("metrics", {
416
eventName,
417
count: currentCount + 1,
418
rate: (currentCount + 1) / ((Date.now() - this.startTime) / 1000),
419
listeners: this.listenerCount(eventName as any)
420
});
421
}
422
423
super.emit(eventName as any, params);
424
}
425
426
getMetrics() {
427
const uptime = (Date.now() - this.startTime) / 1000;
428
const metrics: any = { uptime, events: {} };
429
430
for (const [eventName, count] of this.eventCounts) {
431
metrics.events[eventName] = {
432
count,
433
rate: count / uptime,
434
listeners: this.listenerCount(eventName as any)
435
};
436
}
437
438
return metrics;
439
}
440
}
441
442
// Usage of advanced patterns
443
const debouncedEmitter = new DebouncedEmitter();
444
debouncedEmitter.on("dataChanged", (data) => {
445
console.log("Debounced data:", data);
446
});
447
448
const aggregator = new EventAggregator();
449
aggregator.on("batch", (items) => {
450
console.log("Batch of items:", items);
451
});
452
453
const resilientEmitter = new ResilientEmitter();
454
resilientEmitter.on("retry", ({ attempt, maxAttempts }) => {
455
console.log(`Retry attempt ${attempt}/${maxAttempts}`);
456
});
457
458
const monitoredEmitter = new MonitoredEmitter();
459
monitoredEmitter.on("metrics", (metrics) => {
460
console.log("Event metrics:", metrics);
461
});
462
```
463
464
### Memory Management
465
466
Best practices for preventing memory leaks in event-driven applications.
467
468
```typescript
469
// ✅ Good: Remove listeners when done
470
const cleanup = () => {
471
emitter.removeAllListeners();
472
// Or remove specific listeners
473
emitter.off("data", specificHandler);
474
};
475
476
// ✅ Good: Use once() for one-time events
477
emitter.once("connected", () => {
478
console.log("Connected - this will only fire once");
479
});
480
481
// ✅ Good: Monitor listener counts
482
if (emitter.listenerCount("data") > 10) {
483
console.warn("Too many data listeners, possible memory leak");
484
}
485
486
// ✅ Good: Set appropriate max listener threshold
487
emitter.setMaxListenerWarningThreshold(20);
488
489
// ❌ Bad: Adding listeners in loops without cleanup
490
for (let i = 0; i < 100; i++) {
491
emitter.on("data", () => { /* handler */ }); // Memory leak!
492
}
493
```