真正能上线的 WebSocket 封装必须实现独立闭环的心跳与重连机制:心跳需验证 pong 响应并设超时判定,重连需指数退避、限制次数、清理定时器及事件引用,断连期间消息要缓存并深拷贝,定时器须绑定实例生命周期。

直接用 new WebSocket() 写业务,十次有九次会在弱网、切后台、锁屏后断连不恢复——这不是你代码写得差,是原生 API 本就不负责兜底。真正能上线的封装,必须把重连和心跳做成独立闭环,且互不干扰。
为什么 onclose 不能当唯一重连触发点
很多封装在 onclose 里直接调 connect(),这在服务端主动踢人时有效,但对网络闪断、NAT 超时、代理中断等场景完全失效:此时 readyState 可能还卡在 1(OPEN),onmessage 却收不到任何数据,连接已“假活”。
- 必须靠心跳响应来验证真实连通性,而不是只信
readyState -
onclose触发时,旧WebSocket实例可能残留未清理的定时器或事件监听,导致多次重连并发 - 重连前必须显式调用
socket.close()并置空引用,否则新实例会与旧事件句柄冲突
心跳检测必须带 pong 响应超时判定
只定时 send('ping') 不够,关键在“没收到 pong 就关”。客户端单方面发 ping,不验证响应,等于没做心跳。
- 服务端需约定返回
"pong"或{"type":"pong"},前端监听onmessage做匹配 - 超时判定要用
setTimeout单独计时,比如心跳间隔设为30000,则超时阈值设为35000(含网络抖动余量) - 连续两次超时才触发
socket.close(),避免偶发延迟误判 - 心跳定时器(
setInterval)和超时定时器(setTimeout)必须分开管理,前者只管发,后者只管等
重连必须带指数退避且限制最大次数
固定间隔重试(如每 3 秒重连)在服务端宕机时会引发雪崩请求;不限次数则可能让客户端卡死在无限循环里。
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
立即学习“前端免费学习笔记(深入)”;
- 基础延迟设为
1000,第n次重连延迟 =Math.min(1000 * Math.pow(2, n - 1), 30000) - 最大重试次数建议设为
5,超过后停止自动重连,交由用户手动触发或上报监控 - 每次重连前清除上一轮的
reconnectTimer和heartbeatTimer,防止定时器堆积 - 重连中状态(
RECONNECTING)要显式维护,避免用户快速点击“重连按钮”触发多例并发
消息发送必须区分状态并缓存未送达消息
连接断开期间调 send() 报错是常态,但业务层不该感知——该由封装层拦截并暂存。
- 仅当
socket.readyState === WebSocket.OPEN且isConnected === true才直发 - 否则推入
msgQueue数组,队列结构建议用{ data, timestamp, retryCount },便于后续按需丢弃或限流 - 重连成功后调
flushMessageQueue(),逐条重发;若某条重发失败且retryCount > 3,可丢弃或抛出警告 - 注意:缓存消息要深拷贝,避免原始对象在重连期间被业务层修改导致发送脏数据
最易被忽略的一点:心跳和重连的定时器必须绑定到当前 WebSocket 实例生命周期,而不是全局作用域。换言之,每次新建 socket,就要新建一套定时器;每次销毁 socket,就要清掉对应所有定时器——漏掉任何一个,都会在后续连接中引发不可预测的重复执行或内存泄漏。

















