onerror不是连接失败的可靠信号,仅捕获底层网络异常(如dns失败、tls中断),不触发于http握手失败;真正连接终态由onclose提供,需依据event.code(如1006异常关闭)和reason做重连与错误处理。

onerror 不等于连接失败
onerror 是一个「异常信号」,不是「终态通知」。它只在底层网络层出问题时触发,比如 DNS 解析失败、TLS 握手被中断、本地防火墙拦截、证书校验失败等。此时 readyState 很可能还是 0(CONNECTING)或刚升到 1(OPEN),但连接实际卡住或已失效。
常见误操作是:在 onerror 里立刻调用 ws.close(),这会抛 InvalidStateError,因为连接根本没真正建立过;也不要在 onerror 里做重连决策——它不携带失败原因,且大概率几毫秒后就跟着触发 onclose。
实操建议:
- 仅用
onerror做日志上报,记录event.type和时间戳,不依赖其内容做业务判断 - 避免在
onerror中修改 UI 状态(如显示“连接失败”),这类反馈应统一由onclose驱动 - 若需调试,可配合浏览器 DevTools 的 Network → WS 标签,查看握手请求的 HTTP 状态码(如 401/404/502)
onclose 才是真正的连接终态
onclose 触发代表连接已断开,无论主动关闭还是异常中断。它的 event.code 和 event.reason 才是判断失败类型的依据。
关键状态码含义:
-
1000:正常关闭(ws.close()或服务端调用close()) -
1006:异常关闭(最常见,表示底层连接丢失,无reason) -
1001:端点离开(如页面卸载、浏览器关闭) -
4001–4999:自定义错误码(需服务端在Sec-WebSocket-Close-Code中透传)
注意:event.reason 是否有值,取决于服务端是否在关闭响应头中设置了 Sec-WebSocket-Close-Reason;多数 Nginx 反代或简单 Node.js 实现默认不传,所以不能假设它总有内容。
握手失败时 onerror 根本不触发
这是最容易踩坑的一点:HTTP 升级阶段的失败(如鉴权失败返回 401、路径错误返回 404、协议升级被拒绝返回 502)不会触发 onerror,而是直接跳过 OPEN 状态,进入 CLOSED,并触发 onclose,且 event.code 通常为 1006,event.reason 为空。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
原因在于:WebSocket 对象创建成功后,浏览器就认为“连接尝试已启动”,后续所有基于 HTTP 的握手响应都归为协议协商结果,不属于 onerror 的捕获范围。
实操建议:
- 不要把
onerror当作“连接失败总开关”,它覆盖不了大部分真实业务失败场景 - 对关键连接(如登录后必须建立的实时信道),可在
new WebSocket(url)前加一次轻量预检,例如用fetch(url, { method: 'HEAD' })验证鉴权和路由,但要注意增加 RTT 开销 - 后端应尽量在 Upgrade 响应中返回明确的
Sec-WebSocket-Close-Code和Sec-WebSocket-Close-Reason,前端才能靠onclose做精准分类
重连逻辑必须基于 onclose + code 判断
可靠重连不能靠 onerror 启动,而要等 onclose 触发后,检查 event.code 是否属于异常范围(如 !== 1000 && !== 1001),再执行指数退避策略。
示例逻辑片段:
let reconnectTimeout = null;
const MAX_RETRY = 5;
let retryCount = 0;
<p>function connect() {
const ws = new WebSocket('wss://api.example.com/socket');</p><p>ws.onclose = function(event) {
if (event.code === 1000 || event.code === 1001) {
return; // 正常关闭,不重连
}</p><pre class="brush:php;toolbar:false;">if (retryCount {
retryCount++;
connect();
}, delay);
}}; }
容易忽略的细节:
- 每次重连前要清空上一轮的
reconnectTimeout,否则多个定时器会叠加触发 - 页面隐藏(
visibilitychange)或休眠时应暂停重连,避免无效请求堆积 -
readyState在重连函数内必须检查,防止重复 new WebSocket 导致内存泄漏
真正难处理的从来不是“怎么写 onerror”,而是理解它为什么几乎不报你关心的错——握手失败、鉴权拒绝、服务不可用,全得靠 onclose 的 code 和服务端配合透传的 reason 来定位。










