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:
- Displays the current connection status (connected/disconnected/reconnecting)
- Shows reconnection attempt count and progress
- Queues messages sent while offline and sends them when reconnected
- Displays network latency in real-time
- Provides a manual "Reconnect" button when automatic reconnection fails
- 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.