一、 宏观层:执行与异常模型

核心逻辑: await 是在 try 领土上挖的“地洞”,它暂时交出执行权,但并未脱离监控。

  • 异步迭代 (for await...of)

  • 非阻塞:它是“按需抽水”。当数据没到时,执行权归还浏览器,页面不会卡死。

  • 原理:底层不断调用 reader.read() 并等待那个被“调度”的 Promise。

  • 异常捕获 (try...catch)

  • 地雷效应:无论流在哪个阶段报错(网络中断、手动 error),所有的 read() Promise 都会被 Reject,从而触发 catch

二、 微观层:字节与内存 (Chunk)

核心逻辑: 流不是数据的副本,而是数据的“接力”。

  • Uint8Array:流传输的基础单元。它是 0-255 的字节序列。
  • 瞬时性:每次 read() 拿到的 value 是一块内存切片。处理完后,它就会被回收或传给下一级。
  • 边界截断:字节流不识语义(可能把一个汉字或单词切断)。需要配合 TextDecoderStream 或手动处理 remainder 逻辑。

三、 调度层:双队列撮合模型 (核心精髓)

这是你理解流为什么能“异步非阻塞”的关键。

1. 内部两个“排队大厅”

  1. 数据队列 (Chunks Queue):已经生产出来,但还没人领走的“货”。
  2. 请求队列 (Read Requests Queue):已经发出的 read() 请求,但还没货发的“订单(Promise)”。

2. 调度逻辑(模拟代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class StreamDispatcher {
constructor() {
this.dataQueue = []; // 货架
this.readRequests = []; // 订单 (存的是 Promise 的 resolve 函数)
}

// 生产者:发货
enqueue(chunk) {
if (this.readRequests.length > 0) {
// 1. 如果有人在等,直接把最前面的“订单”兑现了
const resolve = this.readRequests.shift();
resolve({ value: chunk, done: false });
} else {
// 2. 没人等,先存进货架
this.dataQueue.push(chunk);
}
}

// 消费者:下单
read() {
if (this.dataQueue.length > 0) {
// 1. 货架有现货,直接打包成已完成的 Promise 给你
return Promise.resolve({ value: this.dataQueue.shift(), done: false });
} else {
// 2. 货架没货,给你一张“欠条” (创建 Pending Promise)
return new Promise((resolve) => {
// 把“还钱/给货”的动作存起来,等 enqueue 时触发
this.readRequests.push(resolve);
});
}
}

// 异常:破产
error(err) {
// 销毁存货,并撕毁所有排队的订单 (Reject 掉所有 Pending 的 Promise)
this.readRequests.forEach(resolve => /* 触发 Reject 信号 */);
}
}

四、 架构层:流水线自动化 (Pipeline)

核心逻辑: 流是模块化的“积木”,通过 pipeThrough 组装。

  • pipeThrough(transformer):连接管道的“接头”。

  • TransformStream:独立的加工模组。

  • writable 端接收原始数据。

  • transform 逻辑进行加工。

  • readable 端吐出成品。

  • 链式调用stream.pipeThrough(解压).pipeThrough(解码).pipeThrough(JSON解析)

五、 心智模型总结

  1. 流是状态机:它有 Readable(可读)、Closed(关掉)、Errored(报废)三种主要状态。
  2. 读数据不是“拿”而是“约”read() 返回的 Promise 是你和调度员的一个约定。
  3. 背压 (Backpressure):当数据队列太满,调度员会告诉生产者“慢点塞”。