直接JSON.parse(event.data)会卡住页面,因为onmessage同步执行且JSON.parse是计算密集型操作,处理5MB JSON可阻塞主线程超400ms,导致渲染停滞、交互失灵;须改用二进制分片+TextDecoder流式解码+queueMicrotask异步解析。

WebSocket流式传输不是“开了连接就能自动流”,必须手动拆帧、分段消费、异步处理,否则大消息一来,主线程立刻卡死。
为什么直接 JSON.parse(event.data) 会卡住页面
浏览器收到 WebSocket 消息后,onmessage 回调在主线程同步执行。如果服务端一次性推送 5MB 的 JSON 数据,JSON.parse() 会阻塞渲染、事件响应长达数百毫秒,用户操作完全失灵。
- 文本帧越大,解析耗时呈非线性增长(1MB 可能 80ms,5MB 常超 400ms)
- 若每秒推 10 条,主线程持续满载,
requestAnimationFrame丢帧、滚动卡顿、输入延迟全出现 - 错误日志里看不到报错,但 Performance 面板里能看到长任务(Long Task)标记为黄色或红色
用 TextDecoder + Uint8Array 流式解码二进制帧
服务端改发二进制帧(opcode=2),前端不再依赖 event.data 自动转字符串,而是用 TextDecoder 分块解码,避免内存暴涨和单次长解析。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 服务端需按固定 chunk 大小(如 64KB)分片发送,每帧带 sequence ID 和 isLast 标志
- 前端用
Uint8Array缓存所有分片,拼接后再解码,而非每次解码整块 -
TextDecoder实例可复用,避免重复初始化开销 - 示例关键逻辑:
let buffer = new Uint8Array(); // 全局缓存
const decoder = new TextDecoder('utf-8');
socket.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
const chunk = new Uint8Array(event.data);
buffer = concatUint8Arrays(buffer, chunk); // 自定义拼接函数
if (isLastChunk(chunk)) { // 依据自定义帧头判断
try {
const jsonStr = decoder.decode(buffer);
const data = JSON.parse(jsonStr); // 此时才真正 parse
handleData(data);
} catch (e) {
console.error('Parse failed:', e);
}
buffer = new Uint8Array(); // 清空
}
}
};
用 setImmediate 或 queueMicrotask 脱离主线程阻塞
即使做了分片,拼接+解析仍可能耗时几十毫秒。必须把它从当前 JS 执行栈中移出,让浏览器有机会渲染和响应。
- 不要用
setTimeout(..., 0)—— 它是宏任务,延迟不可控且可能被其他定时器挤压 - 优先用
queueMicrotask(() => { ... })(现代浏览器支持),保证在当前任务结束后立即执行,又不打断渲染 - Node.js 环境下用
setImmediate;前端兼容旧版可用Promise.resolve().then(...) - 注意:不能把整个
onmessage包进微任务——那会丢失帧顺序,只包解析和业务逻辑
后端分片必须带校验与重传机制
前端流式处理的前提是后端分片可靠。裸 TCP 层不保证顺序和到达,WebSocket 帧本身也不提供应用层重传。
- 每个分片加 CRC32 或简单 XOR 校验,前端收到后验证,失败则丢弃并通知重发
- 服务端维护 per-connection 的分片窗口(如滑动窗口大小 4),避免无节制堆积
- 客户端收到乱序帧(比如先到 #3 再到 #1),应缓存并等待缺失帧,超时(如 500ms)后触发重传请求
- 别依赖 WebSocket 的
bufferedAmount判断是否可发——它只反映 OS 缓冲区,不反映应用层接收状态
真正难的不是“怎么流”,而是“怎么让流不断、不错、不爆内存”。分片大小、校验方式、重试阈值、缓存淘汰策略——这些参数没一个能拍脑袋定,得结合你的数据特征和用户设备实测调整。

















