WebSockets & Real-Time Apps

Error Handling and Reconnection

20 min Lesson 12 of 35

Error Handling and Reconnection

Network issues, server restarts, and connection failures are inevitable in real-time applications. Robust error handling and automatic reconnection strategies ensure your application remains resilient and provides a seamless user experience.

Types of Connection Errors

WebSocket connections can fail for various reasons:

  • Network failures: Lost internet connection, DNS issues, firewall blocks
  • Server errors: Server crash, restart, or overload
  • Authentication failures: Invalid or expired tokens
  • Transport errors: WebSocket upgrade failure, HTTP polling errors
  • Timeout errors: Ping timeout, connection timeout

Client-Side Error Handling

Socket.io provides several events for handling connection errors on the client side:

<script> const socket = io('http://localhost:3000'); // Connection error (before connection established) socket.on('connect_error', (error) => { console.error('Connection error:', error.message); if (error.message === 'Invalid token') { // Redirect to login window.location.href = '/login'; } else if (error.message === 'timeout') { showNotification('Connection timeout. Retrying...', 'warning'); } }); // General error event socket.on('error', (error) => { console.error('Socket error:', error); showNotification('An error occurred: ' + error.message, 'error'); }); // Connection lost after being established socket.on('disconnect', (reason) => { console.log('Disconnected:', reason); if (reason === 'io server disconnect') { // Server forcefully disconnected, manual reconnection needed showNotification('You have been disconnected by the server', 'error'); socket.connect(); } else if (reason === 'io client disconnect') { // Manual disconnect, don't reconnect showNotification('Disconnected', 'info'); } else { // Automatic reconnection will be attempted showNotification('Connection lost. Reconnecting...', 'warning'); } }); // Reconnection attempt socket.io.on('reconnect_attempt', (attemptNumber) => { console.log(`Reconnection attempt #${attemptNumber}`); updateStatusBadge('reconnecting', attemptNumber); }); // Reconnection failed socket.io.on('reconnect_error', (error) => { console.error('Reconnection error:', error); }); // Successfully reconnected socket.on('connect', () => { console.log('Connected/Reconnected'); showNotification('Connected!', 'success'); updateStatusBadge('connected'); }); </script>
Note: The disconnect event provides a reason parameter that helps you determine whether reconnection should be automatic or manual.

Server-Side Error Handling

Handle errors gracefully on the server to prevent crashes and provide meaningful feedback:

// Server-side error handling io.on('connection', (socket) => { console.log('Client connected:', socket.id); // Handle errors in event handlers socket.on('sendMessage', async (data, callback) => { try { // Validate data if (!data.message || typeof data.message !== 'string') { throw new Error('Invalid message format'); } if (data.message.length > 1000) { throw new Error('Message too long (max 1000 characters)'); } // Process message const result = await saveMessageToDatabase(data); // Send success response callback({ success: true, messageId: result.id }); } catch (error) { console.error('Error in sendMessage:', error); // Send error to client callback({ success: false, error: error.message }); // Optionally emit error event socket.emit('messageError', { message: error.message, timestamp: Date.now() }); } }); // Global error handler for this socket socket.on('error', (error) => { console.error('Socket error:', socket.id, error); }); socket.on('disconnect', (reason) => { console.log('Client disconnected:', socket.id, reason); }); }); // Global uncaught error handler io.engine.on('connection_error', (error) => { console.error('Connection error:', error); });

Automatic Reconnection Configuration

Socket.io automatically attempts to reconnect when the connection is lost. You can configure the reconnection behavior:

<script> const socket = io('http://localhost:3000', { reconnection: true, // Enable automatic reconnection reconnectionAttempts: 5, // Max reconnection attempts reconnectionDelay: 1000, // Initial delay (1 second) reconnectionDelayMax: 5000, // Max delay (5 seconds) randomizationFactor: 0.5, // Randomize delay (±50%) timeout: 20000, // Connection timeout (20 seconds) }); // Monitor reconnection progress let reconnectCount = 0; socket.io.on('reconnect_attempt', () => { reconnectCount++; updateUI({ status: 'reconnecting', attempt: reconnectCount, maxAttempts: 5 }); }); socket.io.on('reconnect_failed', () => { updateUI({ status: 'failed', message: 'Failed to reconnect after 5 attempts' }); // Offer manual reconnection showReconnectButton(); }); socket.on('connect', () => { reconnectCount = 0; updateUI({ status: 'connected', message: 'Connected successfully' }); }); </script>
Tip: Use exponential backoff (increasing delay) to avoid overwhelming the server with reconnection attempts. Socket.io does this automatically.

Custom Reconnection Logic

Implement custom reconnection strategies for specific scenarios:

<script> class SmartReconnection { constructor(socket) { this.socket = socket; this.maxAttempts = 10; this.attemptCount = 0; this.baseDelay = 1000; this.isReconnecting = false; this.setupListeners(); } setupListeners() { this.socket.on('disconnect', (reason) => { if (reason === 'io server disconnect') { // Server kicked us out, wait longer before reconnecting this.reconnectWithDelay(5000); } else { // Network issue, reconnect quickly this.reconnectWithDelay(1000); } }); this.socket.on('connect', () => { this.attemptCount = 0; this.isReconnecting = false; console.log('Reconnected successfully'); }); } reconnectWithDelay(delay) { if (this.isReconnecting || this.attemptCount >= this.maxAttempts) { return; } this.isReconnecting = true; this.attemptCount++; // Exponential backoff const waitTime = Math.min( delay * Math.pow(2, this.attemptCount - 1), 30000 // Max 30 seconds ); console.log(`Reconnecting in ${waitTime}ms (attempt ${this.attemptCount}/${this.maxAttempts})`); setTimeout(() => { if (!this.socket.connected) { this.socket.connect(); this.isReconnecting = false; } }, waitTime); } manualReconnect() { this.attemptCount = 0; this.socket.connect(); } } const socket = io('http://localhost:3000', { autoConnect: false, reconnection: false // Disable automatic reconnection }); const reconnection = new SmartReconnection(socket); socket.connect(); </script>

Connection State Management

Track and display the connection state to keep users informed:

<script> class ConnectionStatus { constructor(socket) { this.socket = socket; this.state = 'disconnected'; this.statusElement = document.getElementById('connection-status'); this.setupListeners(); this.updateUI(); } setupListeners() { this.socket.on('connect', () => { this.setState('connected'); }); this.socket.on('disconnect', () => { this.setState('disconnected'); }); this.socket.io.on('reconnect_attempt', (attempt) => { this.setState('reconnecting', attempt); }); this.socket.io.on('reconnect_failed', () => { this.setState('failed'); }); this.socket.on('connect_error', (error) => { this.setState('error', null, error.message); }); } setState(state, attempt = null, message = null) { this.state = state; this.attempt = attempt; this.message = message; this.updateUI(); } updateUI() { const statusConfig = { connected: { color: '#10b981', text: 'Connected', icon: '●' }, disconnected: { color: '#ef4444', text: 'Disconnected', icon: '●' }, reconnecting: { color: '#f59e0b', text: `Reconnecting (${this.attempt})`, icon: '○' }, failed: { color: '#ef4444', text: 'Connection Failed', icon: '×' }, error: { color: '#ef4444', text: this.message || 'Error', icon: '!', } }; const config = statusConfig[this.state]; this.statusElement.style.color = config.color; this.statusElement.textContent = `${config.icon} ${config.text}`; } } const socket = io('http://localhost:3000'); const status = new ConnectionStatus(socket); </script>

Handling Temporary Network Loss

Implement strategies to handle temporary disconnections gracefully:

<script> class OfflineQueue { constructor(socket) { this.socket = socket; this.queue = []; this.isOnline = socket.connected; this.socket.on('connect', () => { this.isOnline = true; this.processQueue(); }); this.socket.on('disconnect', () => { this.isOnline = false; }); } emit(event, data, callback) { if (this.isOnline) { this.socket.emit(event, data, callback); } else { // Queue the event for later this.queue.push({ event, data, callback }); console.log(`Queued event: ${event} (${this.queue.length} in queue)`); } } processQueue() { console.log(`Processing ${this.queue.length} queued events`); while (this.queue.length > 0) { const { event, data, callback } = this.queue.shift(); this.socket.emit(event, data, callback); } } } const socket = io('http://localhost:3000'); const queue = new OfflineQueue(socket); // Use queue instead of socket directly queue.emit('sendMessage', { message: 'Hello' }, (response) => { console.log('Message sent:', response); }); </script>
Warning: Be careful with queuing sensitive operations like payments or data deletions. Some operations should fail immediately when offline rather than being queued.

Heartbeat and Ping Monitoring

Monitor connection health using ping/pong mechanisms:

<script> const socket = io('http://localhost:3000', { pingInterval: 10000, // Send ping every 10 seconds pingTimeout: 5000 // Wait 5 seconds for pong }); let lastPing = Date.now(); socket.io.on('ping', () => { lastPing = Date.now(); }); socket.io.on('pong', (latency) => { console.log(`Latency: ${latency}ms`); updateLatencyDisplay(latency); // Alert if latency is too high if (latency > 1000) { showWarning('High latency detected. Connection may be unstable.'); } }); // Check for stale connection setInterval(() => { const timeSinceLastPing = Date.now() - lastPing; if (timeSinceLastPing > 30000) { console.warn('No ping received for 30 seconds. Connection may be dead.'); socket.disconnect(); socket.connect(); } }, 5000); </script>
Exercise: Build a resilient chat application that:
  1. Displays the current connection status (connected/disconnected/reconnecting)
  2. Shows reconnection attempt count and progress
  3. Queues messages sent while offline and sends them when reconnected
  4. Displays network latency in real-time
  5. Provides a manual "Reconnect" button when automatic reconnection fails
  6. Notifies users when connection quality degrades (high latency)

Test by stopping the server, sending messages, and restarting the server to verify queued messages are sent.