readablestream.tee() 是零拷贝分流而非克隆——它通过共享底层分发器同步推送同一chunk至两个独立分支,不复制内存,支持并发读取且互不干扰,但要求原始流未锁定且须在消费前调用。

ReadableStream.tee() 并不是“克隆”,而是**零拷贝分流**——它不复制数据,只让两个新流共享原始流的读取逻辑,从而实现真正独立、并发、互不干扰的双重消费。
为什么 tee() 不是克隆,但效果更优
调用 tee() 后返回的是两个全新 ReadableStream 实例,它们:
- 共用底层分发器,同一 chunk 会同步推送到两个分支,顺序完全一致
- 不触发
Uint8Array或ArrayBuffer的内存拷贝,无额外驻留开销 - 各自可独立调用
getReader()、pipeTo()或for await...of - 任一分支
cancel()或出错,不会中断另一分支(除非原始流本身终止)
必须满足的前提条件
tee() 能正常工作的硬性要求:
- 原始流不能已被锁定:即不能已有活跃的
reader,也不能正在执行pipeTo()或pipeThrough() - 必须在流开始消费前调用 —— 一旦
reader.read()返回第一个 chunk,再调用tee()就会抛错 - 调用
tee()后,原始流即失效,后续所有操作只能通过两个分支流进行
典型双重消费场景写法
例如从 fetch 响应中同时记录日志并解析 JSON:
const response = await fetch('/api/data');
const [logStream, parseStream] = response.body.tee();
// 分支 A:记录传输量
logStream.pipeTo(new WritableStream({
write(chunk) {
console.log('已接收:', chunk.length, '字节');
}
}));
// 分支 B:逐块解析 JSON 流(如使用 JSON.parseStreaming)
const reader = parseStream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 处理 value(Uint8Array),例如送入 JSON 解析器
}
常见误区与替代方案对比
不要用以下方式代替 tee():
-
response.body.clone():会缓存已读 chunk 并重放,存在内存堆积风险,且不支持字节流精确控制 - 手动用
TransformStream+controller.enqueue()复制:必然触发 ArrayBuffer 拷贝,失去零开销优势 - 重复调用
fetch:引入额外网络请求和延迟,破坏流式语义
tee() 是目前唯一由标准规范定义、能兼顾无拷贝、低延迟、高并发的多路消费原生方案。











