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:

Common Standard Close Codes


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:

  1. event.code: The integer status code.
  2. event.reason: A string explaining the closure reason sent by the peer.
  3. 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:

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: