WebSocket 断线重连如何避免多个定时器叠加
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 规范。示意图为原创静态关系图,不代表本机真实运行截图。
Go template 执行时报 function not defined 怎么修
- 上一篇
- Go template 执行时报 function not defined 怎么修
- 下一篇
- Go time.Date 如何处理月末日期自动进位
-
- 文章 · 前端 | 1小时前 |
- Fetch AbortController 如何取消超时请求
- 108浏览 收藏
-
- 文章 · 前端 | 3小时前 | javascript · 前端性能 · IntersectionObserver · 图片懒加载 IntersectionObserver 前端性能
- IntersectionObserver 如何只加载进入视口的图片
- 491浏览 收藏
-
- 文章 · 前端 | 6小时前 | Service Worker · 前端缓存 · Cache API ·
- Service Worker 更新后如何避免旧缓存继续返回
- 442浏览 收藏
-
- 文章 · 前端 | 7小时前 | Fetch API · ReadableStream · 流式渲染 · Fetch ReadableStream TextDecoder 前端流式渲染
- ReadableStream 如何把 Fetch 响应分块显示到页面
- 304浏览 收藏
-
- 文章 · 前端 | 8小时前 | 前端 · View Transition API · 浏览器 API · 前端动画 View Transition API view-transition-name
- 浏览器 View Transition 如何只动画列表中的一个元素
- 483浏览 收藏
-
- 文章 · 前端 | 1天前 | 前端 · web components · 生命周期 · 自定义元素 · document Web Components Custom Elements adoptedCallback adoptNode importNode
- Web Components adoptedCallback 什么时候触发
- 382浏览 收藏
-
- 文章 · 前端 | 1天前 |
- Service Worker Cache API 更新资源如何避免旧缓存覆盖
- 215浏览 收藏
-
- 文章 · 前端 | 1天前 |
- WebSocket close code 1006 为何没有服务端原因
- 154浏览 收藏
-
- 文章 · 前端 | 1天前 | 异步编程 · IndexedDB · 前端存储 · 事务 await IndexedDB TransactionInactiveError
- IndexedDB 事务为何不能跨 await
- 221浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 104次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 20次使用
-
- OpenCompass
- OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
- 31次使用
-
- AGI-Eval
- AGI-Eval是由上海交大等高校联合发布的大模型评测社区,提供公正透明的LLM能力榜单、多领域评测集及Data Studio数据服务,助力AI模型性能评估与NLP科研开发。
- 21次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 258次使用
-
- golang基于websocket通信tcpkeepalive研究记录
- 2022-12-23 311浏览
-
- golang实现一个简单的websocket聊天室功能
- 2022-12-28 447浏览
-
- Golang使用WebSocket通信的实现
- 2022-12-26 493浏览
-
- 使用Go语言创建WebSocket服务的实现示例
- 2022-12-28 156浏览
-
- golang websocket 服务端的实现
- 2023-02-16 370浏览

