一个WebSocket的前端封装类

一、概述

实现一个MyWebSocket的自定义 WebSocket 类,用于建立与服务器的 WebSocket 连接,并提供了一系列方法来处理连接状态、发送和接收消息、自动重连等功能。该类可以方便地在前端项目中实现与服务器的实时通信。

二、实现思路

  1. 类的构造函数接收 URL 和可选的配置参数,初始化 WebSocket 连接所需的属性,如连接 URL、重连间隔、最大重连次数、日志级别和各种回调函数等。
  2. 通过_initWebsocket方法初始化 WebSocket 连接,设置连接的打开、关闭、错误和消息接收等事件的处理函数。
  3. 提供了getStatus方法用于获取 WebSocket 的连接状态。
  4. then方法用于设置接收消息时的回调函数。
  5. sendMessage方法用于向服务器发送 JSON 格式的消息,并在发送前自动添加用户信息(如果消息中没有operatingUserId,则从localStorage中获取userId添加到消息中)。
  6. 当连接出现错误或关闭时,启动自动重连机制,尝试在一定时间间隔后重新建立连接。

三、方法解析

  1. constructor:初始化类的属性,合并默认配置和传入的配置选项,调用_initWebsocket方法开始建立连接。
  2. getStatus:返回 WebSocket 的当前连接状态,通过readyState属性判断连接的状态。
  3. then:设置接收消息时的回调函数,该回调函数在_websocketmessage方法中被调用。
  4. sendMessage:在发送消息前,先检查消息是否为有效的 JSON 对象,如果不是则抛出错误。然后调用_wrapUserInfo方法添加用户信息,最后将消息转换为 JSON 字符串并通过 WebSocket 发送。
  5. _wrapUserInfo:检查消息中是否存在operatingUserId,如果不存在则从localStorage中获取userId并添加到消息中。
  6. _isJSON:判断一个对象是否为有效的 JSON 对象,通过检查对象的类型和键的数量来判断。
  7. _initWebsocket:尝试建立 WebSocket 连接,设置连接的各种事件处理函数。如果建立连接过程中出现错误,则启动重连机制。
  8. _websocketopen_websocketerror_websocketclose_websocketmessage:分别处理 WebSocket 的打开、错误、关闭和消息接收事件,调用相应的回调函数和日志方法。
  9. _startReconnect:启动重连机制,设置定时器,在一定时间间隔后尝试重新建立连接,直到达到最大重连次数。
  10. _logInfo_logWarn_logError:根据日志级别打印不同类型的日志信息。

四、注意事项

  1. 确保在使用该类时,提供正确的 WebSocket 服务器 URL 和有效的配置选项。
  2. 注意处理可能出现的错误,如消息不是有效的 JSON 对象、获取tokenuserId失败等情况。
  3. 在使用自动重连功能时,要考虑到重连次数和时间间隔的合理性,避免过度频繁的重连对服务器造成压力。
  4. 确保在合适的时机调用then方法设置接收消息的回调函数,以便正确处理接收到的消息。
  5. 注意在发送消息前,确保消息是有效的 JSON 对象,否则会抛出错误。

五、使用示例

this.MyWebSocket = new MyWebSocket("http://ip:port/websocket");
// 接收消息
this.MyWebSocket.then((e) => {
  console.log(e);
});
// 发送消息
this.websocket.sendMessage(JSON.stringify({"info": "我正在操作页面..." }));

六、附源码

const _OPERATING_USER_ID = "userId";
/**
 * WebSocket Custom Class
 */
class MyWebSocket {
  _url = "";
  _ws = null;
  _then = null;
  _options = null;
  _reconnectInterval = 2000;
  _reconnectTimeoutId = null;
  _reconnectAttempts = 0;
  _maxReconnectAttempts = 100;
  _logLevel = process.env.NODE_ENV === "development" ? "info" : "warning";
  _connectedCallback = null;
  _disconnectedCallback = null;
  _errorCallback = null;
  _messageReceivedCallback = null;

  /**
   * Constructor for MyWebSocket class. Initializes a new WebSocket connection with the provided URL and options.
   *
   * @param {string} url - The WebSocket server URL.
   * @param {Object} [options={}] - Optional configuration options for the WebSocket connection.
   * @param {number} [options.reconnectInterval=2000] - The time interval (in milliseconds) between reconnection attempts.
   * @param {number} [options.maxReconnectAttempts=100] - The maximum number of reconnection attempts before giving up.
   * @param {string} [options.logLevel="info"] - The log level for WebSocket events. Can be "off", "error", "warn", "info", or "debug".
   * @param {function} [options.connectedCallback] - A callback function to be executed when the WebSocket connection is established.
   * @param {function} [options.disconnectedCallback] - A callback function to be executed when the WebSocket connection is closed.
   * @param {function} [options.errorCallback] - A callback function to be executed when an error occurs during the WebSocket connection.
   * @param {function} [options.messageReceivedCallback] - A callback function to be executed when a message is received from the WebSocket server.
   */
  constructor(url, options = {}) {
    this._url = url;
    this._options = {
      reconnectInterval: this._reconnectInterval,
      maxReconnectAttempts: this._maxReconnectAttempts,
      logLevel: "info",
      ...options,
    };
    this._reconnectInterval = this._options.reconnectInterval;
    this._maxReconnectAttempts = this._options.maxReconnectAttempts;
    this._logLevel = this._options.logLevel;
    this._connectedCallback = this._options.connectedCallback;
    this._disconnectedCallback = this._options.disconnectedCallback;
    this._errorCallback = this._options.errorCallback;
    this._messageReceivedCallback = this._options.messageReceivedCallback;
    this._initWebsocket();
  }
  /**
   * Retrieves the readyState of the WebSocket connection.
   *
   * @returns {number} The readyState of the WebSocket connection.
   * The readyState attribute represents the state of the connection:
   * - 0: CONNECTING: The connection is not yet open.
   * - 1: OPEN: The connection is open and ready to communicate.
   * - 2: CLOSING: The connection is in the process of closing.
   * - 3: CLOSED: The connection is closed or couldn't be opened.
   */
  getStatus() {
    return this._ws.readyState;
  }
  /**
   * Sets a callback function to be executed when a message is received from the WebSocket server.
   *
   * @param {function} callback - The callback function to be executed when a message is received.
   * The callback function will receive two parameters:
   * - {Object} msg - The parsed JSON message received from the server.
   * - {MessageEvent} event - The original MessageEvent object received from the WebSocket.
   *
   * @returns {void}
   */
  then(callback) {
    this._then = callback;
  }

  /**
   * Sends a JSON message to the WebSocket server.
   *
   * @param {Object} message - The JSON message to be sent.
   * @throws {Error} Throws an error if the message is not a valid JSON object.
   * @returns {void}
   */
  sendMessage(message) {
    if (!this._isJSON(message)) {
      throw new Error(" could not send non JSON message");
    }
    this._wrapUserInfo(message);
    this._ws.send(JSON.stringify(message));
  }

  /**
   * Wraps the user information in the given message object.
   * If the message does not contain the operatingUserId, it retrieves the userId from localStorage and adds it to the message.
   *
   * @param {Object} message - The message object to be sent to the WebSocket server.
   * @returns {void} The function modifies the message object in-place.
   *
   * @example
   * const message = {
   *   type: 'chat',
   *   content: 'Hello, WebSocket!'
   * };
   * _wrapUserInfo(message);
   * // After execution, message will be:
   * // {
   * //   type: 'chat',
   * //   content: 'Hello, WebSocket!',
   * //   operatingUserId: '12345'
   * // }
   */
  _wrapUserInfo(message) {
    if (!(_OPERATING_USER_ID in message)) {
      message.operatingUserId = localStorage.getItem("userId");
    }
  }

  _isJSON(obj) {
    if (typeof obj !== "object") {
      return false;
    }
    try {
      return Object.keys(obj).length > 0 && typeof obj === "object";
    } catch (e) {
      return false;
    }
  }

  /**
   * Initializes a new WebSocket connection with the provided URL.
   *
   * @private
   * @memberof MyWebSocket
   * @returns {void}
   *
   * @throws {Error} Throws an error if there is an issue initializing the WebSocket connection.
   */
  _initWebsocket() {
    // config.headers["Authorization"] = localStorage.getItem("token");
    try {
      const token = localStorage.getItem("token");
      if (!token) {
        this._logError("get token failed.");
      }
      const url = `${this._url}?Authorization=${token}`;
      this._ws = new WebSocket(url);
      const that = this;
      this._ws.onopen = (e) => {
        return this._websocketopen(that, e);
      };
      this._ws.onclose = (e) => {
        return this._websocketclose(that, e);
      };
      this._ws.onmessage = (e) => {
        return this._websocketmessage(that, e);
      };
      this._ws.onerror = (e) => {
        return this._websocketerror(that, e);
      };
    } catch (error) {
      this._logError("Error initializing WebSocket: " + error.message);
      this._startReconnect();
    }
  }

  _websocketopen(that) {
    that._logInfo("websocket open");
    if (typeof that._connectedCallback === "function") {
      that._connectedCallback();
    }
    if (that._reconnectTimeoutId) {
      clearInterval(that._reconnectTimeoutId);
      that._reconnectTimeoutId = null;
      that._reconnectAttempts = 0;
    }
  }

  _websocketerror(that, event) {
    that._logError("websocket error: " + event.message);
    if (typeof that._errorCallback === "function") {
      that._errorCallback(event);
    }
    that._startReconnect();
  }

  _websocketclose(that, event) {
    that._logWarn("websocket closed: " + event.reason);
    if (typeof that._disconnectedCallback === "function") {
      that._disconnectedCallback(event);
    }
    that._startReconnect();
  }

  /**
   * Handles incoming messages from the WebSocket server.
   * Parses the received data as JSON and triggers the appropriate callback functions.
   *
   * @param {MessageEvent} event - The original MessageEvent object received from the WebSocket.
   * @private
   * @memberof MyWebSocket
   * @returns {void}
   */
  _websocketmessage(that, event) {
    let msg = {};

    try {
      msg = JSON.parse(event.data);
    } catch (e) {
      that._logError("websocket message parse error: " + e.message);
    }
    if (that._then && typeof that._then === "function") {
      that._then(msg, event);
    }
    if (typeof this._messageReceivedCallback === "function") {
      that._messageReceivedCallback(msg, event);
    }
  }

  /**
   * Initiates the reconnection process to the WebSocket server.
   * This function is called when the WebSocket connection is closed or encounters an error.
   * It checks if the maximum reconnection attempts have been reached, logs an error message if necessary,
   * and starts a timer to attempt reconnection after the specified interval.
   *
   * @private
   * @memberof MyWebSocket
   * @returns {void}
   */
  _startReconnect() {
    if (this._reconnectTimeoutId) {
      return;
    }
    this._reconnectTimeoutId = setInterval(() => {
      this._logInfo("Attempting to reconnect... Attempt " + (this._reconnectAttempts + 1));
      this._initWebsocket();
      this._reconnectAttempts++;

      if (this._reconnectAttempts >= this._maxReconnectAttempts) {
        this._logError("Max reconnect attempts reached. Giving up.");
        clearInterval(this._reconnectTimeoutId);
        return;
      }
    }, this._reconnectInterval);
  }

  /**
   * Logs an informational message to the console based on the log level.
   *
   * @param {string} message - The informational message to be logged.
   * @returns {void}
   *
   * @private
   * @memberof MyWebSocket
   */
  _logInfo(message) {
    if (this._logLevel === "info" || this._logLevel === "debug") {
      console.log(message);
    }
  }

  /**
   * Logs a warning message to the console based on the log level.
   *
   * @param {string} message - The warning message to be logged.
   * @returns {void}
   *
   * @private
   * @memberof MyWebSocket
   */
  _logWarn(message) {
    if (this._logLevel === "warn" || this._logLevel === "info" || this._logLevel === "debug") {
      console.warn(message);
    }
  }

  /**
   * Logs an error message to the console based on the log level.
   *
   * @param {string} message - The error message to be logged.
   * @returns {void}
   *
   * @private
   * @memberof MyWebSocket
   */
  _logError(message) {
    if (this._logLevel !== "off") {
      console.error(message);
    }
  }
}
### 前端 WebSocket 封装方法及示例 为了实现更稳定可靠的 WebSocket 连接管理,通常会将 WebSocket 功能封装一个独立的类或模块中。这不仅有助于简化代码逻辑,还能增强错误处理能力和自动重连机制。 #### 创建可重连的 WebSocket 类 通过定义一个 `WebSocket` 类来封装基本功能,并加入自动重连特性: ```javascript class ReconnectingWebSocket { constructor(url, protocols = [], options = {}) { this.url = url; this.protocols = protocols; this.options = options; this.reconnectInterval = options.reconnectInterval || 1000; // 默认重连间隔时间设为1秒 this.maxReconnectAttempts = options.maxReconnectAttempts || Infinity; // 设置最大尝试次数 this._ws = null; this._reconnectCount = 0; this.connect(); } connect() { try { console.log(`Connecting to ${this.url}`); this._ws = new WebSocket(this.url, this.protocols); this._ws.onopen = () => { console.log('Connected'); this._resetReconnectCounter(); }; this._ws.onerror = (error) => { console.error('Error:', error); }; this._ws.onclose = () => { console.warn('Connection closed. Attempting reconnect...'); setTimeout(() => this.reconnect(), this.reconnectInterval); }; this._ws.onmessage = (event) => { const message = JSON.parse(event.data); this.emit('message', message); }; } catch (e) { console.error(e.message); setTimeout(() => this.reconnect(), this.reconnectInterval); } } _resetReconnectCounter() { this._reconnectCount = 0; } reconnect() { if (++this._reconnectCount >= this.maxReconnectAttempts) { console.error('Max reconnection attempts reached.'); return; } this.connect(); } send(data) { if (!this.isConnected()) throw new Error('Not connected'); this._ws.send(JSON.stringify(data)); } close(code, reason) { if (this._ws && this._ws.readyState === WebSocket.OPEN) { this._ws.close(code, reason); } } isConnected() { return this._ws?.readyState === WebSocket.OPEN; } emit(eventName, data) { window.dispatchEvent(new CustomEvent(eventName, { detail: data })); } } ``` 此段代码实现了带有自动重试机制的 WebSocket 客户端[^3]。每当连接断开时,客户端会在指定的时间间隔后再次尝试建立新的连接直到达到预设的最大重试次数为止。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丷丩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值