用Promise封装带有心跳机制的websocket

423 阅读4分钟

前言

封装一个带有心跳重连机制的 WebSocket,其好处在于:

  1. 自动重连机制:当 WebSocket 连接断开或发生错误时,封装的代码会自动进行重连,以保持持续的连接。这样可以提高应用程序的稳定性和可靠性,避免因为网络或服务器问题导致连接中断而无法及时恢复。
  2. Promise 的使用:通过将 WebSocket 封装为 Promise,可以更方便地使用 Promise 的特性。例如,可以使用 then 方法处理连接成功后的逻辑,使用 catch 方法处理连接失败或发生错误的情况。这样可以使代码更加清晰、易读,并且可以统一处理异步操作的结果和错误。
  3. 可读性和可维护性:封装带有心跳重连机制的 WebSocket 可以提高代码的可读性和可维护性。通过将连接、重连和事件处理等逻辑分离,使代码结构更清晰。同时,使用 Promise 和相应的错误处理机制,可以更好地处理连接失败和错误,使代码更健壮、易于调试和维护。

下面是示例代码:

Promise封装的WebSocket

export class MyWebSocket {
    constructor(url) {
        this.url = url;
 
        // close来源判断及后续操做
        this.closeConfig = {
            resolve: null,
            closing: false
        }
        // promise池
        this.promisePool = {};
    }
    
    tokenCheck(req, rsp) {
    // 此处根据本身的数据结构进行tokenCheck的判断,返回一个boolean
    }
    
    open() {
        return new Promise((resolve, reject) => {
            if (typeof this._websocket === 'undefined') {
                this._websocket = new WebSocket(this.url);
                this._websocket.open = (e) => {
                    resolve({e, ws: this});
                };
                this._websocket.onerror = (e) => {
                    reject(e);
                }
            }
            this._websocket.onclose = (e) => {
                // 非主动close
                if (!this.closeConfig.closing) {
                    console.log('reconnect');     
                    // 对应的重连操做     
                }
                // 若手动close,恢复初始状态
                this.closeConfig.closing = false;
            }
            
            this._websocket.onmessage = (e) => {         
                const key = e.content.token;    
                const req = this.promisePool[key]
                req.resolve(e);
                delete this.promisePool[key];
            };
        });
    }
    
    close() {
        this.closeConfig.closing = true;
        this._websocket.close();
    }
    // token包含在content中
    send(name, content) {
        return new Promise((resolve, reject) => {
          this.promisePool[content.token] = {
            content,
            resolve,
            reject,
            name
          };
          this._websocket.send({name, content});
    });
}
async test () {
    const ws = new MyWebSocket('www.mywebsocket.com');
    await ws.open();
    await ws.send(.....).then(()=>{...});
    await ws.send(.....).then(()=>{...});
}

带心跳机制的WebSocket

<html>
<head>
  <meta charset="utf-8">
  <title>WebSocket Demo</title>
</head>
<body>
  <script type="text/javascript">
    // let ws = new WebSocket("wss://echo.websocket.org");
    /*
    ws.onerror = function(e) {
      console.log('已关闭');
    };
    ws.onopen = function(e) {
      console.log('握手成功');
      ws.send('123456789');
    }
    ws.onclose = function() {
      console.log('已关闭');
    }
    ws.onmessage = function(e) {
      console.log('收到消息');
      console.log(e);
    }
    */
    
    let lockReconnect = false;//避免重复连接
    let wsUrl = "wss://echo.websocket.org";
    let ws;
    let tt;
    function createWebSocket() {
      try {
        ws = new WebSocket(wsUrl);
        init();
      } catch(e) {
        console.log('catch');
        reconnect(wsUrl);
      }
    }
    function init() {
      ws.onclose = function () {
        console.log('链接关闭');
        reconnect(wsUrl);
      };
      ws.onerror = function() {
        console.log('发生异常了');
        reconnect(wsUrl);
      };
      ws.onopen = function () {
        //心跳检测重置
        heartCheck.start();
      };
      ws.onmessage = function (event) {
        //拿到任何消息都说明当前连接是正常的
        console.log('接收到消息');
        heartCheck.start();
      }
    }
    function reconnect(url) {
      if(lockReconnect) {
        return;
      };
      lockReconnect = true;
      //没连接上会一直重连,设置延迟避免请求过多
      tt && clearTimeout(tt);
      tt = setTimeout(function () {
        createWebSocket(url);
        lockReconnect = false;
      }, 4000);
    }
    //心跳检测
    let heartCheck = {
      timeout: 3000,
      timeoutObj: null,
      serverTimeoutObj: null,
      start: function(){
        console.log('start');
        let self = this;
        this.timeoutObj && clearTimeout(this.timeoutObj);
        this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
        this.timeoutObj = setTimeout(function(){
          //这里发送一个心跳,后端收到后,返回一个心跳消息,
          console.log('55555');
          ws.send("123456789");
          self.serverTimeoutObj = setTimeout(function() {
            console.log(111);
            console.log(ws);
            ws.close();
            // createWebSocket();
          }, self.timeout);

        }, this.timeout)
      }
    }
    createWebSocket(wsUrl);
  </script>
</body>
</html>

结合下来

优化前

export class MyWebSocket {
  constructor(url) {
    this.url = url;

    // close来源判断及后续操作
    this.closeConfig = {
      resolve: null,
      closing: false
    };

    // promise池
    this.promisePool = {};
  }

  tokenCheck(req, rsp) {
    // 此处根据本身的数据结构进行tokenCheck的判断,返回一个boolean
  }

  open() {
    return new Promise((resolve, reject) => {
      if (typeof this._websocket === 'undefined') {
        this._websocket = new WebSocket(this.url);
        this._websocket.onopen = (e) => {
          resolve({ e, ws: this });
        };
        this._websocket.onerror = (e) => {
          reject(e);
        };
      }

      this._websocket.onclose = (e) => {
        // 非主动close
        if (!this.closeConfig.closing) {
          console.log('reconnect');
          // 对应的重连操作

          // 延迟一段时间后重新打开连接
          setTimeout(() => {
            this.open().then(resolve).catch(reject);
          }, 5000); // 5秒延迟
        }
        // 若手动close,恢复初始状态
        this.closeConfig.closing = false;
      };

      this._websocket.onmessage = (e) => {
        const key = e.content.token;
        const req = this.promisePool[key];
        req.resolve(e);
        delete this.promisePool[key];
      };
    });
  }

  close() {
    this.closeConfig.closing = true;
    this._websocket.close();
  }

  // token包含在content中
  send(name, content) {
    return new Promise((resolve, reject) => {
      this.promisePool[content.token] = {
        content,
        resolve,
        reject,
        name
      };
      this._websocket.send(JSON.stringify({ name, content }));
    });
  }
}

代码看起来已经相当完善了,但还是有一些可以改进的地方:

  1. connect() 方法中,你可以使用 resolve()reject() 的方式来返回 Promise,而不是使用 setTimeoutclearTimeout。这样可以更好地控制连接超时的情况。
  2. reconnect() 方法中,你可以通过检查 this.websocket.readyState 的值来避免重复创建 WebSocket 连接。
  3. send() 方法中,可以将判断连接状态和发送消息的逻辑合并,减少重复代码。

下面是根据上述建议进行改进的代码示例:

export class MyWebSocket {
  constructor(url) {
    this.url = url;
    this.websocket = null;
    this.promisePool = {};
    this.reconnectInterval = 3000;
    this.closeConfig = { resolve: null, closing: false };
  }

  tokenCheck(req, rsp) {
    // 根据自身的数据结构进行 tokenCheck 的判断,返回一个 boolean
  }

  connect() {
    return new Promise((resolve, reject) => {
      if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
        resolve(this);
      } else {
        this.websocket = new WebSocket(this.url);

        this.websocket.addEventListener('open', () => {
          console.log('WebSocket connection established.');
          resolve(this);
        });

        this.websocket.addEventListener('message', (event) => {
          const { name, content } = event.data;
          const key = content.token;
          if (key && this.promisePool[key]) {
            const req = this.promisePool[key];
            req.resolve(event);
            delete this.promisePool[key];
          }
        });

        this.websocket.addEventListener('error', (error) => {
          console.error('WebSocket error:', error);
          reject(error);
        });

        this.websocket.addEventListener('close', () => {
          console.log('WebSocket connection closed.');
          if (!this.closeConfig.closing) {
            this.reconnect();
          }
          this.closeConfig.closing = false;
        });
      }
    });
  }

  reconnect() {
    if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) {
      this.websocket = null; // 清空旧的 WebSocket 对象
      setTimeout(async () => {
        try {
          await this.connect();
        } catch (error) {
          console.error('WebSocket reconnect failed:', error);
        }
      }, this.reconnectInterval);
    }
  }

  open() {
    return this.connect();
  }

  close() {
    this.closeConfig.closing = true;
    this.websocket.close();
  }

  send(name, content) {
    return new Promise((resolve, reject) => {
      const sendOrQueue = () => {
        if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
          this.websocket.send(JSON.stringify({ name, content }));
          this.promisePool[content.token] = { content, resolve, reject, name };
        } else {
          setTimeout(sendOrQueue, 100); // 等待连接建立后再发送消息
        }
      };

      sendOrQueue();
    });
  }
}


如何让WebSocket断网重连更快速?

www.163.com/dy/article/…

juejin.cn/post/694505…