你以为 WebSocket 收快了只会 OOM?今天 Chrome 124 把背压机制彻底原生化了
消息来了接不住,浏览器 OOM 了——这是所有做过 AI 流式对话界面的工程师都踩过的坑。
问题不在于消息本身,而在于旧 WebSocket API 根本没有”接不接得住”这个概念。服务端以每秒几百条的速度往客户端灌 tokens,客户端只能靠 onmessage 事件被动接收,消息全堆在浏览器内存里,最终要么 OOM,要么 CPU 跑满。bufferedAmount 轮询是唯一的出路,但那个手感用过的人都知道——根本不是 proper 的背压机制。
WebSocketStream 把这件事彻底变了。
Chrome 124(2025 年 4 月稳定版,默认开启)引入的 WebSocketStream 接口,核心思路是把 WebSocket 连接本身变成两个标准流:opened promise 直接返回 ReadableStream 和 WritableStream,你可以用标准的 Streams API 语义来处理连接。
背压这件事,终于”免费”了。
你写一个消费端的循环:
const wss = new WebSocketStream('wss://your-ai-server/stream');
const { readable, writable } = await wss.opened;
const reader = readable.getReader();
const writer = writable.getWriter();
while (true) {
const { value, done } = await reader.read();
if (done) break;
// 这里是你的渲染逻辑——接 AI 流式输出、画图表、处理传感器数据
render(value);
// 不需要手动暂停,不需要轮询 bufferedAmount
// 如果 render() 慢,流的内部队列就会自动给服务器施压
// 服务端收到背压后会暂停发送,直到客户端腾出处理能力
}
这就是关键区别:旧 API 是事件驱动的,无法表达”我处理不过来了”;新 API 是流驱动的,Streams API 的背压语义直接映射到 WebSocket 协议层。服务端收到背压后自动暂停发送,客户端这边只需要正常 await reader.read(),不需要任何额外代码。
写数据也一样:
// 发数据不用轮询 bufferedAmount 了
await writer.write(JSON.stringify({ type: 'ack', cursor: currentPosition }));
// 如果底层发送缓冲区满了,write() 会自动等,不会丢数据
这套机制对于 AI 流式接口尤其有价值。Claude Code、Copilot Chat 这样的工具,后端本质上是 WebSocket 流式推送 tokens,客户端渲染跟不上 tokens 到达速度是家常便饭。之前的解法是客户端主动发 ping 帧或轮询计数,现在背压直接走协议层,服务器自然知道客户端的能力边界。
三步落地:
第一,特性检测必须做,目前只有 Chrome/Edge 支持:
if (!('WebSocketStream' in self)) {
console.warn('当前浏览器不支持 WebSocketStream,回退到普通 WebSocket');
// 回退逻辑:用旧 API + bufferedAmount 轮询(效果差一些)
}
第二,写一个统一的流封装,以后换浏览器也能无缝回退:
async function createStreamingWS(url) {
if ('WebSocketStream' in self) {
const wss = new WebSocketStream(url);
const { readable, writable } = await wss.opened;
return { readable, writable, close: () => wss.close() };
}
// 回退到普通 WebSocket
return createFallbackWS(url);
}
第三,等待 Firefox 和 Safari 信号。WHATWG 标准化进度在 whatwg/websockets#48,目前只有 Chromium 实现了,但 Chrome 全球份额足够大,生产环境已经可以渐进增强接入。
总结一句话: 背压不是新概念,但把它原生做进 WebSocket 协议层,意味着所有基于流的应用——AI 流式对话、实时数据仪表板、工业传感器推送——第一次不用自己造轮子了。
评论区
登录后可评论。