WebSocket Close Codes and JavaScript Reconnection
This article provides a practical overview of WebSocket close codes and outlines effective JavaScript strategies for handling network reconnections. You will learn the meaning of standard and custom WebSocket status codes, how the browser captures connection terminations, and how to implement resilient reconnection logic using exponential backoff and jitter to keep real-time applications stable.
Understanding WebSocket Close Codes
When a WebSocket connection terminates, the endpoint initiating the closure sends a 16-bit integer known as a close code, optionally accompanied by a human-readable reason string. These codes categorize why a connection was closed, allowing the client to determine whether it should attempt to reconnect or abort completely.
WebSocket close codes are categorized into specific numeric ranges:
- 0–999: Reserved and unused.
- 1000–2999: Defined by the WebSocket protocol standard (RFC 6455).
- 3000–3999: Reserved for libraries, frameworks, and standardized specifications.
- 4000–4999: Reserved for private use by applications.
Common Standard Close Codes
- 1000 (Normal Closure): The connection successfully completed its purpose. Reconnection is usually not required.
- 1001 (Going Away): An endpoint is terminating the connection because it is navigating away (e.g., a browser tab closing) or the server is shutting down.
- 1002 (Protocol Error): The connection terminated due to a protocol violation.
- 1003 (Unsupported Data): An endpoint received a data type it cannot accept (e.g., binary instead of text).
- 1006 (Abnormal Closure): A reserved code indicating the connection dropped unexpectedly without sending a close frame (e.g., network drop, dropped TCP connection, or server crash). This code cannot be set manually and is generated by the client environment.
- 1008 (Policy Violation): The connection was terminated because an endpoint violated a general policy (often used for authentication or authorization failures).
- 1011 (Internal Server Error): The server encountered an unexpected condition that prevented it from fulfilling the request.
Capturing Close Events in JavaScript
JavaScript provides the WebSocket.prototype.onclose
event listener to monitor when a connection ends. The event object
contains three primary properties:
event.code: The integer status code.event.reason: A string explaining the closure reason sent by the peer.event.wasClean: A boolean indicating whether the connection closed via a clean closing handshake (true) or abruptly (false).
const socket = new WebSocket('wss://example.com/ws');
socket.onclose = (event) => {
console.log(`Closed with code: ${event.code}`);
console.log(`Reason: ${event.reason}`);
console.log(`Clean closure: ${event.wasClean}`);
};JavaScript Reconnection Strategies
A naive reconnection strategy that reconnects instantly or at fixed intervals can overwhelm servers during outages (the “thundering herd” problem). Robust client architectures use conditional logic based on close codes, exponential backoff, and jitter.
1. Conditional Reconnection Based on Codes
Not all closures should trigger a reconnect:
- Do not reconnect: Code
1000(intentional disconnect),1008(invalid credentials), or custom authentication failure codes (e.g.,4001). - Do reconnect: Code
1006(network loss),1001(server restart), or any temporary server error.
2. Exponential Backoff with Jitter
Exponential backoff progressively increases the delay between reconnection attempts by multiplying the wait time by a factor (usually 2) after each failed attempt, up to a defined maximum. Adding “jitter” introduces randomness to spread out reconnect requests from multiple clients across a time window.
\[\text{Delay} = \min(\text{maxDelay}, \text{baseDelay} \times 2^{\text{attempts}}) + \text{randomJitter}\]
Implementation Example
The following pattern demonstrates a complete WebSocket client with conditional reconnection, exponential backoff, and jitter:
class ResilientWebSocket {
constructor(url) {
this.url = url;
this.socket = null;
this.reconnectAttempts = 0;
this.baseDelay = 1000; // 1 second
this.maxDelay = 30000; // 30 seconds
this.maxAttempts = 10;
this.isExplicitlyClosed = false;
this.connect();
}
connect() {
this.isExplicitlyClosed = false;
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log('Connected to server');
this.reconnectAttempts = 0; // Reset attempts on success
};
this.socket.onmessage = (event) => {
console.log('Message received:', event.data);
};
this.socket.onclose = (event) => {
if (this.isExplicitlyClosed) {
console.log('Connection closed intentionally. Not reconnecting.');
return;
}
// Do not reconnect on normal closure or authentication failure
if (event.code === 1000 || event.code === 1008) {
console.warn(`Connection closed with code ${event.code}. No retry.`);
return;
}
this.scheduleReconnect();
};
this.socket.onerror = (error) => {
console.error('WebSocket encountered an error:', error);
// onerror is usually followed immediately by onclose
};
}
scheduleReconnect() {
if (this.reconnectAttempts >= this.maxAttempts) {
console.error('Max reconnection attempts reached. Halting.');
return;
}
// Exponential backoff calculation
const exponentialDelay = Math.min(
this.maxDelay,
this.baseDelay * Math.pow(2, this.reconnectAttempts)
);
// Add jitter (random value between 0 and 1000ms)
const jitter = Math.random() * 1000;
const delay = exponentialDelay + jitter;
this.reconnectAttempts++;
console.log(`Reconnecting in ${Math.round(delay)}ms (Attempt ${this.reconnectAttempts})`);
setTimeout(() => {
this.connect();
}, delay);
}
close() {
this.isExplicitlyClosed = true;
if (this.socket) {
this.socket.close(1000, 'Client closed connection');
}
}
}
// Usage
const client = new ResilientWebSocket('wss://example.com/socket');Integrating Browser Network Events
In browser environments, you can optimize reconnection strategies
further by listening to network state changes using
window.addEventListener:
onlineevent: When network connectivity is restored after an outage, trigger an immediate connection attempt instead of waiting for a long backoff timer to expire.offlineevent: Pause reconnection timers while the device has no internet access to conserve client resources.