要实现“满带宽、零卡顿”的流式数据处理,必须全程绕开主线程:Worker 内 fetch 获取 ReadableStream,用 TransformStream 逐块解析,通过 Transferable Streams 零拷贝传输,由 Worker 主导流控与背压,并支持超时降级。

要让流式数据处理真正实现“满带宽、零卡顿”,关键不是把流塞进 Worker 就完事,而是让整个数据流的接收、解析、转换、聚合全程绕开主线程——从网络字节流入口开始就卸载到 Worker,中间不落地、不拷贝、不阻塞。
流式数据必须从 fetch 响应体源头接入 Worker
常见误区是:主线程用 fetch 拿到完整响应后,再把 JSON 或 ArrayBuffer 传给 Worker。这已失去“流”的意义,且大体积数据序列化会卡主线程上百毫秒。
正确做法是直接在 Worker 内发起 fetch,并用 ReadableStream + TransformStream 处理分块数据:
- Worker 脚本中调用 fetch(url).then(res => res.body) 获取原生 ReadableStream
- 用 TransformStream 实现逐 chunk 解析(如 JSON 行、CSV 行、protobuf 帧)
- 解析结果直接写入另一个 TransformStream 输出,或累积成批次后 postMessage 回主线程
- 主线程只负责创建 Worker、监听 onmessage,完全不参与任何数据处理逻辑
零拷贝流式通信:用 Transferable Streams(Chrome 120+ / Edge 120+ 支持)
传统 postMessage 传 chunk 数组仍需结构化克隆。现代浏览器支持将 ReadableStream 直接 transfer 到主线程:
- Worker 中构造 new ReadableStream({ pull }),内部按需生成处理后的数据流
- 调用 postMessage({ stream }, [stream.getReader().read().value?.chunk]) 不可行;正确方式是使用 transferable streams API
- 主线程收到 message 后,用 response.body.pipeTo(new WritableStream(...)) 或直接绑定到 canvas、audio、blob URL
- 若需兼容旧版,退回到 ArrayBuffer + transferable:每个 chunk 解析为 Uint8Array,postMessage(buffer, [buffer])
流控与背压必须由 Worker 主导
主线程无法可靠控制流速,一旦 push 过快,Worker 内部 buffer 溢出会导致丢帧或 OOM。必须由 Worker 自主调节拉取节奏:
- Worker 使用 reader.read() 显式控制每次读取量,不依赖 on('data')
- 内置滑动窗口计数器:当前处理中 chunk ≤ 3 个时才 read 下一个,避免积压
- 主线程仅发轻量 control message(如 { type: 'pause' } / { type: 'resume' }),不干预数据流本身
- 超时自动降级:连续 5 秒无新 chunk 到达,触发心跳重连或 fallback 到全量加载
真实场景下的端到端链路示例
比如实时语音识别流(ASR):
- Worker 内 fetch('wss://asr-api/v1/stream') → 得到 ReadableStream
- 用 TransformStream 将二进制音频帧解码为 PCM,再送入 WebAssembly ASR 模型
- 识别结果以 { type: 'transcript', text: 'xxx', ts: 1234 } 格式,通过 postMessage 推送
- 主线程只做三件事:渲染文字、高亮时间轴、转发给 TTS 模块 —— 全部异步非阻塞
- 全程无 DOM 操作、无 setTimeout 模拟、无内存中缓存整段音频
不复杂但容易忽略










