Core tools and utilities for the web3.js ecosystem, providing foundational layer functionality for blockchain interactions.
—
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.
Core event emitter implementation with type-safe event handling and listener management.
/**
* Web3 event emitter with type-safe event handling and listener management
* @template T - Event map type defining available events and their data types
*/
class Web3EventEmitter<T extends Web3EventMap = Web3EventMap> implements Web3Emitter<T> {
constructor();
// Event listener management
on<K extends Web3EventKey<T>>(eventName: K, fn: Web3EventCallback<T[K]>): void;
once<K extends Web3EventKey<T>>(eventName: K, fn: Web3EventCallback<T[K]>): void;
off<K extends Web3EventKey<T>>(eventName: K, fn: Web3EventCallback<T[K]>): void;
// Event emission
emit<K extends Web3EventKey<T>>(eventName: K, params: T[K]): void;
// Listener inspection
listenerCount<K extends Web3EventKey<T>>(eventName: K): number;
listeners<K extends Web3EventKey<T>>(eventName: K): Function[];
eventNames(): (string | symbol)[];
// Management
removeAllListeners(): EventEmitter;
setMaxListenerWarningThreshold(maxListenersWarningThreshold: number): void;
getMaxListeners(): number;
}Usage Examples:
import { Web3EventEmitter } from "web3-core";
// Define event map for type safety
interface MyEvents {
dataReceived: { data: string; timestamp: number };
error: { message: string; code: number };
connected: { address: string };
disconnected: undefined;
}
// Create typed event emitter
const emitter = new Web3EventEmitter<MyEvents>();
// Add event listeners with type safety
emitter.on("dataReceived", ({ data, timestamp }) => {
console.log(`Received data: ${data} at ${new Date(timestamp)}`);
});
emitter.on("error", ({ message, code }) => {
console.error(`Error ${code}: ${message}`);
});
emitter.on("connected", ({ address }) => {
console.log(`Connected to ${address}`);
});
emitter.on("disconnected", () => {
console.log("Disconnected");
});
// Emit events
emitter.emit("dataReceived", { data: "Hello World", timestamp: Date.now() });
emitter.emit("error", { message: "Connection failed", code: 500 });
emitter.emit("connected", { address: "127.0.0.1:8080" });
emitter.emit("disconnected", undefined);
// One-time listener
emitter.once("connected", ({ address }) => {
console.log(`First connection to ${address}`);
});
// Remove specific listener
const errorHandler = ({ message, code }: { message: string; code: number }) => {
console.error(`Error: ${message}`);
};
emitter.on("error", errorHandler);
emitter.off("error", errorHandler);
// Inspect listeners
console.log("Error listeners:", emitter.listenerCount("error"));
console.log("All event names:", emitter.eventNames());Promise-Event hybrid that combines Promise functionality with event emission for long-running operations.
/**
* Promise-like object that also emits events during execution
* @template ResolveType - Type of the final resolved value
* @template EventMap - Event map for intermediate events during execution
*/
class Web3PromiEvent<ResolveType, EventMap extends Web3EventMap = Web3EventMap>
extends Web3EventEmitter<EventMap>
implements Promise<ResolveType> {
constructor(executor: PromiseExecutor<ResolveType>);
// Promise interface
then<TResult1 = ResolveType, TResult2 = never>(
onfulfilled?: ((value: ResolveType) => TResult1 | PromiseLike<TResult1>) | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
): Promise<TResult1 | TResult2>;
catch<TResult = never>(
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
): Promise<ResolveType | TResult>;
finally(onfinally?: (() => void) | null): Promise<ResolveType>;
// Enhanced event methods that return this for chaining
on<K extends Web3EventKey<EventMap>>(eventName: K, fn: Web3EventCallback<EventMap[K]>): this;
once<K extends Web3EventKey<EventMap>>(eventName: K, fn: Web3EventCallback<EventMap[K]>): this;
// Promise symbol
readonly [Symbol.toStringTag]: 'Promise';
}
/**
* Promise executor function type
* @template T - Type of resolved value
*/
type PromiseExecutor<T> = (
resolve: (value: T) => void,
reject: (reason: unknown) => void
) => void;Usage Examples:
import { Web3PromiEvent } from "web3-core";
// Define event map for transaction process
interface TransactionEvents {
sending: { txHash: string };
sent: { txHash: string };
transactionHash: { txHash: string };
confirmation: { confirmationNumber: number; receipt: any };
}
// Create PromiEvent for transaction sending
function sendTransaction(txData: any): Web3PromiEvent<any, TransactionEvents> {
return new Web3PromiEvent((resolve, reject) => {
// Simulate transaction process
setTimeout(() => {
const txHash = "0x1234567890abcdef...";
// Emit sending event
promiEvent.emit("sending", { txHash });
setTimeout(() => {
// Emit sent event
promiEvent.emit("sent", { txHash });
promiEvent.emit("transactionHash", { txHash });
// Simulate confirmations
let confirmationCount = 0;
const confirmationInterval = setInterval(() => {
confirmationCount++;
promiEvent.emit("confirmation", {
confirmationNumber: confirmationCount,
receipt: { txHash, status: 1, blockNumber: 18000000 + confirmationCount }
});
if (confirmationCount >= 3) {
clearInterval(confirmationInterval);
resolve({ txHash, confirmations: confirmationCount });
}
}, 1000);
}, 2000);
}, 1000);
});
}
// Use PromiEvent - can be used as both Promise and EventEmitter
const txPromiEvent = sendTransaction({ to: "0x742d35Cc6634C0532925a3b8D0d3", value: "1000000000000000000" });
// Listen to events during execution
txPromiEvent
.on("sending", ({ txHash }) => {
console.log("Transaction sending:", txHash);
})
.on("sent", ({ txHash }) => {
console.log("Transaction sent:", txHash);
})
.on("transactionHash", ({ txHash }) => {
console.log("Transaction hash received:", txHash);
})
.on("confirmation", ({ confirmationNumber, receipt }) => {
console.log(`Confirmation ${confirmationNumber}:`, receipt.blockNumber);
});
// Use as Promise
txPromiEvent
.then((result) => {
console.log("Transaction completed:", result);
})
.catch((error) => {
console.error("Transaction failed:", error);
});
// Or with async/await
try {
const result = await txPromiEvent;
console.log("Transaction successful:", result.txHash);
} catch (error) {
console.error("Transaction error:", error);
}Type definitions for creating type-safe event maps and handlers.
/**
* Base event map type - maps event names to their data types
*/
type Web3EventMap = Record<string, unknown>;
/**
* Event key type - ensures key exists in event map
* @template T - Event map type
*/
type Web3EventKey<T extends Web3EventMap> = string & keyof T;
/**
* Event callback function type
* @template T - Event data type
*/
type Web3EventCallback<T> = (params: T) => void | Promise<void>;
/**
* Event emitter interface definition
* @template T - Event map type
*/
interface Web3Emitter<T extends Web3EventMap> {
on<K extends Web3EventKey<T>>(eventName: K, fn: Web3EventCallback<T[K]>): void;
once<K extends Web3EventKey<T>>(eventName: K, fn: Web3EventCallback<T[K]>): void;
off<K extends Web3EventKey<T>>(eventName: K, fn: Web3EventCallback<T[K]>): void;
emit<K extends Web3EventKey<T>>(eventName: K, params: T[K]): void;
}Usage Examples:
// Define complex event map
interface BlockchainEvents {
blockReceived: {
blockNumber: number;
blockHash: string;
timestamp: number;
transactions: string[]
};
transactionPending: {
txHash: string;
from: string;
to: string;
value: string
};
error: {
code: number;
message: string;
details?: any
};
connected: {
provider: string;
networkId: number
};
disconnected: undefined;
}
// Create typed emitter
class BlockchainMonitor extends Web3EventEmitter<BlockchainEvents> {
constructor() {
super();
this.setMaxListenerWarningThreshold(50); // Higher threshold for blockchain events
}
startMonitoring() {
// Simulate blockchain monitoring
setInterval(() => {
this.emit("blockReceived", {
blockNumber: Math.floor(Math.random() * 1000000),
blockHash: `0x${Math.random().toString(16).substr(2, 64)}`,
timestamp: Date.now(),
transactions: [`0x${Math.random().toString(16).substr(2, 64)}`]
});
}, 12000); // Every 12 seconds
}
reportError(code: number, message: string, details?: any) {
this.emit("error", { code, message, details });
}
}
// Use typed monitor
const monitor = new BlockchainMonitor();
monitor.on("blockReceived", ({ blockNumber, blockHash, timestamp, transactions }) => {
console.log(`Block ${blockNumber} (${blockHash}) received at ${new Date(timestamp)}`);
console.log(`Contains ${transactions.length} transactions`);
});
monitor.on("error", ({ code, message, details }) => {
console.error(`Blockchain error ${code}: ${message}`, details);
});
monitor.startMonitoring();Common patterns and best practices for event-driven Web3 applications.
/**
* Event listener management utilities
*/
listenerCount<K extends Web3EventKey<T>>(eventName: K): number;
listeners<K extends Web3EventKey<T>>(eventName: K): Function[];
eventNames(): (string | symbol)[];
removeAllListeners(): EventEmitter;
setMaxListenerWarningThreshold(maxListenersWarningThreshold: number): void;
getMaxListeners(): number;Usage Examples:
// Pattern: Event debouncing
class DebouncedEmitter extends Web3EventEmitter<{ dataChanged: string }> {
private debounceTimer?: NodeJS.Timeout;
emitDebounced(data: string) {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
this.debounceTimer = setTimeout(() => {
this.emit("dataChanged", data);
}, 300); // 300ms debounce
}
}
// Pattern: Event aggregation
class EventAggregator extends Web3EventEmitter<{ batch: string[] }> {
private buffer: string[] = [];
private batchTimer?: NodeJS.Timeout;
add(item: string) {
this.buffer.push(item);
if (!this.batchTimer) {
this.batchTimer = setTimeout(() => {
this.emit("batch", [...this.buffer]);
this.buffer = [];
this.batchTimer = undefined;
}, 1000); // Batch every second
}
}
}
// Pattern: Error handling with retry
class ResilientEmitter extends Web3EventEmitter<{
data: any;
error: Error;
retry: { attempt: number; maxAttempts: number }
}> {
async emitWithRetry(data: any, maxAttempts = 3) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
this.emit("data", data);
return; // Success
} catch (error) {
this.emit("retry", { attempt, maxAttempts });
if (attempt === maxAttempts) {
this.emit("error", error as Error);
throw error;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
}
// Pattern: Event metrics and monitoring
class MonitoredEmitter extends Web3EventEmitter<{ data: any; metrics: any }> {
private eventCounts = new Map<string, number>();
private startTime = Date.now();
emit<K extends string>(eventName: K, params: any): void {
// Track event metrics
const currentCount = this.eventCounts.get(eventName) || 0;
this.eventCounts.set(eventName, currentCount + 1);
// Emit metrics periodically
if (currentCount % 100 === 0) {
this.emit("metrics", {
eventName,
count: currentCount + 1,
rate: (currentCount + 1) / ((Date.now() - this.startTime) / 1000),
listeners: this.listenerCount(eventName as any)
});
}
super.emit(eventName as any, params);
}
getMetrics() {
const uptime = (Date.now() - this.startTime) / 1000;
const metrics: any = { uptime, events: {} };
for (const [eventName, count] of this.eventCounts) {
metrics.events[eventName] = {
count,
rate: count / uptime,
listeners: this.listenerCount(eventName as any)
};
}
return metrics;
}
}
// Usage of advanced patterns
const debouncedEmitter = new DebouncedEmitter();
debouncedEmitter.on("dataChanged", (data) => {
console.log("Debounced data:", data);
});
const aggregator = new EventAggregator();
aggregator.on("batch", (items) => {
console.log("Batch of items:", items);
});
const resilientEmitter = new ResilientEmitter();
resilientEmitter.on("retry", ({ attempt, maxAttempts }) => {
console.log(`Retry attempt ${attempt}/${maxAttempts}`);
});
const monitoredEmitter = new MonitoredEmitter();
monitoredEmitter.on("metrics", (metrics) => {
console.log("Event metrics:", metrics);
});Best practices for preventing memory leaks in event-driven applications.
// ✅ Good: Remove listeners when done
const cleanup = () => {
emitter.removeAllListeners();
// Or remove specific listeners
emitter.off("data", specificHandler);
};
// ✅ Good: Use once() for one-time events
emitter.once("connected", () => {
console.log("Connected - this will only fire once");
});
// ✅ Good: Monitor listener counts
if (emitter.listenerCount("data") > 10) {
console.warn("Too many data listeners, possible memory leak");
}
// ✅ Good: Set appropriate max listener threshold
emitter.setMaxListenerWarningThreshold(20);
// ❌ Bad: Adding listeners in loops without cleanup
for (let i = 0; i < 100; i++) {
emitter.on("data", () => { /* handler */ }); // Memory leak!
}Install with Tessl CLI
npx tessl i tessl/npm-web3-core