一、 顶层架构:浏览器作为“大管家”

在 Web Streams 的世界里,浏览器引擎(如 Chrome 的 V8/Blink)是最高统治者。你写的代码只是向浏览器注册了三个插件(Hooks)

  • 控制权反向:你从不主动调用 pullwrite,是浏览器根据水位线下游需求来回拨动这些开关。
  • 节奏大师:浏览器通过控制 Promise 的 pendingresolved 状态,精细地掐住了每一片数据的流动速度。

二、 三大 API:标准化插件接口

1. ReadableStream (生产插件)

  • 本质:定义“数据从哪来”以及“怎么拿”。
  • API 核心
1
2
3
4
5
6
7
8
9
10
const reader = new ReadableStream(
{
// 【钩子】:当浏览器发现内部队列空了,且下游张嘴要货时,主动调你
async pull(controller) {
const data = await getRawData();
controller.enqueue(data); // 把货交给浏览器的“中介”
},
},
{ highWaterMark: 1 },
); // 告诉浏览器:我这最多存几个分片

2. TransformStream (加工插件)

  • 本质:定义“数据怎么变”。它是双向的,既接上游也送下游。
  • API 核心
1
2
3
4
5
6
7
const transformer = new TransformStream({
// 【钩子】:当浏览器把上游拿到的货塞给你时触发
transform(chunk, controller) {
const newData = doMagic(chunk);
controller.enqueue(newData); // 加工完,传给浏览器的下一个口袋
},
});

3. WritableStream (消费插件)

  • 本质:定义“数据去哪儿”。它是整条线的终点。
  • API 核心
1
2
3
4
5
6
7
const writer = new WritableStream({
// 【钩子】:当浏览器拿到货,让你存盘时触发
async write(chunk) {
await saveToDisk(chunk);
// 💡 只要你不 resolve,浏览器就会让整条流水线原地等待!
},
});

三、 本质原理:中介与“双口袋”逻辑

流的底层不是简单的函数调用,而是由浏览器维护的双队列模型

1. 两个口袋(中介的账本)

  • 口袋 A (数据队列):存放生产者 enqueue 进来,但还没被拿走的 Chunk。
  • 口袋 B (请求队列):存放消费者 read() 发出,但还没拿到货的 Promise。

2. 撮合逻辑 (Match-making)

  • **消费者发起 read()**

  • 口袋 A 有货?直接拿货,响应完成。

  • 口袋 A 没货?把消费者的 Promise 塞进口袋 B 挂起

  • **生产者发起 enqueue()**

  • 口袋 B 有人在等?立刻取出 Promise 并 Resolve(叫醒消费者)。

  • 口袋 B 没人等?把货放进口袋 A 存着


四、 负压机制 (Backpressure) 的完美闭环

这是流最伟大的设计:通过 Promise 的延迟实现“压力反向传导”

  1. 下游拥堵WritableStream.write() 处理得慢,返回的 Promise 处于 pending
  2. 管道卡住:浏览器内部的搬运循环(Pipe)正在 await 这个 Promise,所以它不去向上游要货。
  3. 上游停工:因为没人调用 read(),浏览器的“数据队列”慢慢填满到 highWaterMark
  4. 钩子休眠:水位满了,浏览器便停止触发你的 pull 钩子。

五、 最终总结:流的“道”与“术”

  • 术 (写法):用 pipeThrough 连工厂,用 pipeTo 连终点。
  • 道 (原理)
  • 数据是一片片流过去的(有序分片)。
  • 压力是一层层顶回去的(Promise 链条)。
  • 调度是浏览器全权负责的(中介撮合)。