websocket.onclose后需清空引用和定时器并延时重连,心跳须配超时兜底,重连应指数退避,页面隐藏时暂停定时器以避免误判。
websocket.onclose 触发后直接调用 new websocket() 会报错
很多开发者在 onclose 回调里直接执行 new websocket(url),结果控制台立刻抛出 websocket is already closed 或 invalidstateerror。这不是代码写错了,而是因为:状态还没真正清理干净,旧 socket 对象还残留着事件监听或未清空的定时器;更关键的是,onclose 可能在 closing 状态下就被触发(readystate=2),此时强行新建连接会冲突。
正确做法是:先显式置空引用、清除所有定时器,再延时重连。例如:
this.socket = null;
clearTimeout(this.pingTimeout);
clearTimeout(this.reconnectTimer);
this.reconnectTimer = setTimeout(() => {
this.connectWebSocket();
}, this.reconnectDelay);
- 必须把
this.socket设为null,否则下次connectWebSocket()会复用旧实例 -
reconnectDelay初始值建议 ≥1000ms,避免短时间高频重连被服务端限流 - 不要在
onclose里直接调用connectWebSocket()—— 必须包一层setTimeout
心跳检测不能只靠 setInterval + send('ping')
单纯用 setInterval 定期发 'ping' 是常见误区。服务端没响应、网络丢包、甚至服务端根本没实现 pong 回复逻辑时,前端完全感知不到“假连”——socket.readyState 还是 OPEN,但实际已断。
健壮的心跳必须配超时兜底:
this.pingTimeout = setTimeout(() => {
console.warn('Ping timeout, closing socket');
this.socket?.close();
}, this.heartbeatTimeout || 10000);
- 每次发 ping 前先
clearTimeout(this.pingTimeout),收到 pong 后再重置 - 服务端返回的必须是明确可识别的
pong消息(比如字符串"pong"或 JSON{"type":"pong"}),不能依赖二进制空包 - 心跳间隔(
heartbeatInterval)建议 ≤30s,要小于 Nginx 默认的proxy_read_timeout(通常60s)
重连次数和延迟必须指数退避
固定 2s 重试 5 次,在真实弱网场景下极易失败。服务端刚重启时可能还没就绪,连续猛冲只会加重负担;移动设备切后台再唤醒,网络恢复也需要时间。
前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。
推荐使用指数退避策略:
this.reconnectDelay = Math.min( this.baseDelay * Math.pow(2, this.reconnectAttempts), 30000 );
-
baseDelay初始设为 1000,第 1 次重连等 1s,第 2 次等 2s,第 3 次等 4s……到 30s 封顶 - 每次重连失败(
onerror或onclose且event.code !== 1000)才计数,正常close(1000)不应触发重连 - 最大重试次数别硬写死成 5 或 10,最好允许外部传入配置,方便不同环境调整
页面隐藏/切后台时必须暂停心跳和重连
Chrome 和 Safari 在页面进入 visibilityState === 'hidden' 后,会节流 setTimeout 和 setInterval,导致心跳超时误判、重连延迟拉长,甚至触发不必要的重连风暴。
务必监听 visibilitychange:
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
clearTimeout(this.pingTimeout);
clearTimeout(this.reconnectTimer);
} else if (this.socket?.readyState !== WebSocket.OPEN) {
this.connectWebSocket();
}
});
- 页面隐藏时清掉所有定时器,但不要主动
close()—— 避免用户切回来还要多等一次握手 - 页面显示时,仅当连接已断开才触发重连;如果还连着,就恢复心跳即可
- 这个逻辑容易被忽略,却是移动端 WebSocket 稳定性的分水岭
重连逻辑最麻烦的从来不是“怎么建新连接”,而是“什么时候不该建”——状态没清干净、心跳没兜底、重试没节奏、页面切后台还狂发请求,这些细节叠加起来,才是生产环境连接频繁闪断的真正原因。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










