应封装带token和子协议的WebSocket构造器:用encodeURIComponent(token)作为protocols第二项,配合心跳消息(非原生ping)、退避重连、定时器清理及readyState校验,确保高可用。

直接用原生 WebSocket 在中大型项目里写三次以上,基本就该抽离了——状态管理混乱、重连逻辑重复、心跳没统一入口、token 传不进 header、断开后页面没感知,全是高频踩坑点。
怎么封装一个带 token 和子协议的 WebSocket 构造器
浏览器原生 WebSocket 构造函数只接受 url 和可选的 protocols 数组,而服务端常靠 Sec-Websocket-Protocol 请求头校验 token。不能把 token 拼在 URL 里(有长度和安全风险),也不能靠 Cookie(跨域失效)。
-
protocols必须是字符串数组,且每个元素需encodeURIComponent处理,否则服务端收不到原始 token - 协议名本身无语义,只是透传通道;常见写法如
["auth-token", "user-123"],其中第二个值才是真实 token - 如果服务端要求固定协议名(比如
"bearer"),那第一个元素就得写死,token 放第二个位置,前端和服务端必须对齐 - 构造时若
protocols为空或undefined,浏览器会忽略该字段,导致认证失败
示例关键片段:
const token = sessionStorage.getItem("token") || "";
const protocols = token ? ["bearer", encodeURIComponent(token)] : [];
this.socket = new WebSocket("wss://api.example.com/ws", protocols);
心跳检测为什么不能只发 ping
只调用 socket.send("ping") 是无效的——WebSocket 协议层的 PING/PONG 帧由浏览器底层自动处理,前端无法主动触发,也收不到 onping 事件。所谓“心跳”,实际是业务层自定义消息,靠定时器 + 服务端响应约定来判断连接活性。
立即学习“前端免费学习笔记(深入)”;
- 必须和服务端约定好心跳消息格式(如
{"type": "HEARTBEAT"})和响应规则(如返回{"type": "PONG"}) - 不能只发不等:发完心跳后要启动一个
setTimeout,超时未收到响应就视为失联,触发重连 - 服务端可能不回心跳、或延迟回,所以客户端要控制最大等待时间(建议 ≤ 3s),避免假在线
- 心跳间隔不宜过短( 60s);55s 是较稳妥的折中值,兼顾 NAT 超时与资源消耗
自动重连怎么避免无限循环和雪崩
网络抖动时,onclose 和 onerror 可能在几毫秒内连续触发多次,如果每次都在回调里立即新建 WebSocket,极易造成连接风暴,压垮服务端或触发风控限流。
- 用布尔锁
lockReconnect控制重连入口,防止并发触发 - 重连前检查
event.code:值为1006(异常关闭)或1001(离开页面)才重连;1000(正常关闭)则跳过 - 采用退避策略:首次延时 1s,第二次 2s,第三次 4s……最大不超过 30s,避免集中重连
- 设置总重连次数上限(如 5~15 次),超过后停止并通知用户,而不是死等
Vue 项目里怎么避免组件卸载后 still trying to send
组件销毁时 beforeUnmount 或 onBeforeUnmount 钩子里只调用 socket.close() 不够——如果此时 socket 正在重连队列中,或心跳定时器还在跑,后续仍可能执行 send() 并报错 WebSocket is already in CLOSING or CLOSED state。
- 必须在关闭前清除所有定时器:
clearTimeout(this.heartBeatTimer)、clearInterval(this.reconnectTimer) - 手动置空引用:
this.socket = null,防止闭包中继续访问已销毁实例 - 发送消息前加双保险判断:
if (this.socket && this.socket.readyState === WebSocket.OPEN),不能只依赖isConnected这类自维护标志 - 如果用了全局单例(如
WebSocketManager),组件卸载时应调用removeMessageListener,避免监听器残留
真正难的不是写通一次连接,而是让连接在页面跳转、标签页切换、网络恢复、服务端滚动发布这些真实场景下不掉链子——每个多余的 setTimeout、每一处没清的定时器、每一次没校验的 readyState,都会在凌晨三点变成告警。


















