WebSocket 断线重连如何避免多个定时器叠加
来源:17golang原创
时间:2026-09-12 18:00:49 205浏览 收藏
WebSocket 断线重连要避免多个定时器叠加,关键不是把 setTimeout 的时间调大,而是让所有断线入口只调用一个调度函数,并在函数内部用 reconnectTimer 做幂等保护。实际实现中只由 close 事件安排下一次连接,error 负责记录原因;连接成功后清零重试次数,停止组件时同时清理计时器和旧连接。
- 一个控制器只保留一个待执行的重连计时器。
- 等待时间按指数增长,并设置最大值和轻微随机抖动。
- 旧 socket 的回调要被忽略,页面销毁时必须 clearTimeout。
先把 WebSocket 重连问题拆成三个状态
浏览器的 WebSocket 对象通过 readyState 表示连接状态,open 表示连接建立,close 表示连接已经关闭,error 表示发生了连接错误。最容易出错的写法是在 onerror 和 onclose 中分别调用重连函数:一次网络故障可能同时经过两个回调,于是每次失败都会多出一条定时器链。
可以先明确三个状态:当前 socket、当前重试次数、当前待执行的计时器。它们由同一个控制器拥有,其他业务代码只调用 start 或 stop,不直接创建定时器。
把重连责任集中在 close 事件
下面的控制器把“创建连接”和“安排重连”分开。error 不再触发第二次重试,真正的调度入口只有 close;scheduleReconnect 开头的计时器判断,则让重复的关闭通知变成无操作。
class ReconnectingSocket {
constructor(url) {
this.url = url;
this.socket = null;
this.reconnectTimer = null;
this.retryCount = 0;
this.stopped = false;
}
start() {
// 重新启动前清除停止标记;真正的连接只从这里创建。
this.stopped = false;
this.connect();
}
connect() {
if (this.stopped) return;
const current = new WebSocket(this.url);
this.socket = current;
current.addEventListener("open", () => {
// 成功后恢复首轮等待,避免下一次断线直接使用长延迟。
if (current !== this.socket) return;
this.retryCount = 0;
console.info("WebSocket 已连接");
});
current.addEventListener("error", () => {
// error 只保留诊断职责;close 事件会负责唯一一次调度。
console.warn("WebSocket 连接发生错误");
});
current.addEventListener("close", () => {
// 忽略旧连接的回调,避免它改写新连接的状态。
if (current !== this.socket || this.stopped) return;
this.scheduleReconnect();
});
}
scheduleReconnect() {
// 已经有计时器时直接返回,保证全局只有一个待执行任务。
if (this.reconnectTimer !== null || this.stopped) return;
const base = Math.min(1000 * 2 ** this.retryCount, 30000);
const jitter = Math.floor(Math.random() * 500);
const delay = base + jitter;
this.retryCount += 1;
console.info(`第 ${this.retryCount} 次重连,等待 ${delay}ms`);
this.reconnectTimer = window.setTimeout(() => {
// 回调开始就归还计时器槽位,下一次失败才能重新预约。
this.reconnectTimer = null;
this.connect();
}, delay);
}
stop() {
// 页面销毁或用户退出时,计时器和连接都必须一起收口。
this.stopped = true;
if (this.reconnectTimer !== null) {
window.clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
if (this.socket) {
this.socket.close(1000, "主动停止");
this.socket = null;
}
}
}

指数退避要有上限,也要能停止
1000 * 2 ** retryCount 让连续失败的请求逐步拉开间隔,30000 是等待上限,随机的 jitter 可以避免多个客户端在同一时刻同时冲击服务端。上限不是越大越好:实时看板通常可以接受几十秒恢复,但应根据业务允许的离线时长调整。
| 场景 | 推荐处理 | 原因 |
|---|---|---|
| 首次断线 | 短延迟重试 | 快速恢复临时网络抖动 |
| 连续失败 | 指数增长并封顶 | 减少无效连接请求 |
| 连接成功 | retryCount 清零 | 下一次故障从短延迟开始 |
| 页面离开 | clearTimeout 后 close | 不让后台回调重新建连 |
这里的计时器只是“未来某个时刻尝试一次”的预约,不代表连接已经恢复。只有 open 回调确认当前 socket 仍是控制器持有的对象,才可以把重试次数归零。

检查旧回调、重复启动和页面销毁
如果旧 socket 已经关闭,而新 socket 正在连接,旧对象稍晚到达的 close 回调不能再次预约计时器,所以示例用 current !== this.socket 做对象身份判断。应用层也要避免重复调用 start;如果确实需要“重置连接”,先调用 stop,再启动新的控制器。
日志至少保留重试次数、等待毫秒数和当前 readyState。当同一时刻出现两条“第 N 次重连”日志,或一个控制器同时拥有两个非空计时器引用,就应回到事件绑定处排查是否还有隐藏的 setInterval、组件重复挂载或 error 回调重试。
常见问题
为什么不建议在 error 和 close 中都重连?
它们可能由同一次连接故障连续触发,两个入口会各自预约任务。让 error 只记录诊断,让 close 统一调度,职责更容易保持唯一。
重连定时器已经 clearTimeout,为什么还会重新连接?
通常是回调已经进入事件循环,或者旧 socket 的 close 回调没有做身份判断。清理计时器之外,还要设置 stopped,并忽略不再属于当前控制器的 socket 回调。
参考资料:MDN WebSocket API、WebSocket close 事件与 WHATWG WebSockets 规范。示意图为原创静态关系图,不代表本机真实运行截图。
-
311 收藏
-
447 收藏
-
493 收藏
-
156 收藏
-
370 收藏
-
108 收藏
-
491 收藏
-
文章 · 前端 | 4小时前 | javascript · web components · custom elements · 前端排错 · Web Components Custom Elements attributeChangedCallback observedAttributes233 收藏
-
442 收藏
-
304 收藏
-
483 收藏
-
文章 · 前端 | 1天前 | 前端 · web components · 生命周期 · 自定义元素 · document Web Components Custom Elements adoptedCallback adoptNode importNode382 收藏
-
215 收藏
-
154 收藏
-
147 收藏
-
107 收藏
-
221 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习