关键在于按 websocket 生命周期分层捕获错误:握手阶段监听 onerror/onclose 并检查状态码;接收循环中仅对 await recv() 单独 try-catch;send/ping 失败需区分连接状态与链路问题;关闭时校验 readystate 并在 finally 中静默 close。

关键不是“加不加 try-catch”,而是“在哪捕、捕什么、怎么分”。WebSocket 异步长连接的错误来源分散在握手、收发、心跳、关闭多个环节,统一包裹整个连接逻辑反而掩盖真实问题。必须按生命周期分层拦截,结合状态码、异常类型和上下文做智能归类。
握手阶段:捕获初始化失败
连接未建立就报错,属于前置校验问题,不应进入主循环。此时错误多为网络不可达、CORS 拦截或认证失败,需单独处理:
- 用 try/catch 包裹 new WebSocket(url) 本身不生效(构造函数同步返回),真正要包的是
ws.onopen之前的ws.onerror和ws.onclose初始事件 - 监听
ws.onopen前的ws.onclose:若event.code === 0或event.reason含 “401”、“403”,大概率是 token 过期或跨域配置错误 - 服务端返回非 101 状态码时,浏览器不会触发
onerror,而是直接走onclose,所以必须检查event.code是否为 4000+ 自定义业务码(如 4001 表示鉴权失败)
接收循环中:只捕 await recv() 的异常
WebSocket 长连接维持靠持续接收或心跳,空闲超时会触发底层断开。recv() 是唯一可能抛出 WebSocketError 的异步操作,必须单独包裹:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 不要把整个
async for循环塞进一个 try 块,而应在每次await ws.recv()前加 try-catch - 捕获到
WebSocketConnectionClosedError时,立即检查ws.closed属性——若为 true,说明对端已发关闭帧;若为 false,大概率是网络中断或代理主动 kill - 遇到
1006(异常关闭)或1009(消息过大),不要重试 recv,应直接触发重连流程并记录原始错误堆栈
发送与心跳环节:区分阻塞型与协议型失败
send() 和 ping() 虽为异步,但失败原因不同。前者常因连接已关闭被拒绝,后者失败则暴露底层链路问题:
-
await ws.send(...)抛错时,优先判断ws.readyState === WebSocket.OPEN,否则是“发送时连接已失效”,属于可预期的竞态,应降级为缓存待发 -
await ws.ping()超时(如返回TimeoutError),说明中间设备(Nginx、CDN)未透传 ping 帧,需调整ping_interval和ping_timeout参数 - 避免在 send 中混入 JSON 序列化:大对象
JSON.stringify()可能同步卡顿,应提前序列化或丢进run_in_executor(Python)/setTimeout(JS)隔离
关闭与清理阶段:用 close() 替代强制终止
主动断开必须发送标准关闭帧,否则对端收到 1006 会误判为异常。异常退出时更要确保资源释放:
- 所有
ws.close()调用前,先检查ws.readyState:仅在OPEN或CLOSING时调用,避免重复 close 报错 - 在 finally 块中执行清理,但不要 await close()——finally 不支持 await,应改用
ws.close().catch(() => {})静默忽略 - 服务端返回
1001(Going Away)时,客户端应停止重连并提示“服务暂不可用”,而非指数退避










