本文主要通过两个例子拆解如何使用异步生成器创建一个慢速数据源,并通过 pipeThrough 管道和 TextEncoderStreamTextDecoderStream 实现数据的编码解码

# 基础概念:流的三位一体

  1. 可读流(ReadableStream):数据的源头。数据从这里流出
  2. 可写流(WritableStream):数据的终点。数据流入这里并被处理
  3. Transform 流(TransformStream):数据的中间处理器。它接收一种格式的输入流,并输出另一种格式的流(例如:输入字符串 \rightarrow 输出字节)

# 示例一:字符串编码与流的创建(源头)

将字符 foo 模拟成每隔一秒到达的数据流,并将其转换为浏览器处理数据所需要的字节格式(UTF-8 编码)

const data = [102, 111, 111]; 
// A. 异步生成器:数据源
async function* chars() {
  const encodedText = data.map((x) => Uint8Array.of(x));
  for (let charChunk of encodedText) {
    // 暂停 1 秒,模拟网络延迟
    yield await new Promise((resolve) => setTimeout(resolve, 1000, charChunk));
  }
}
// B. 可读流:封装数据源
const encodedTextStream = new ReadableStream({
  async start(controller) {
    // 遍历异步生成器,将数据推入流中
    for await (let charChunk of chars()) {
      controller.enqueue(charChunk);
    }
    controller.close(); // 数据发送完毕,关闭流
  },
});
// C. 消费者:读取字节数据
const reader = encodedTextStream.getReader();
(async function () {
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    console.log("字节数据:", value); 
  }
})();
  1. 数据源的格式:每个字符都是一个 Uint8Array 字节数组,每个字节对应一个字符的 UTF-8 编码,所有涉及网络传输或文件 I/O 的原始数据流,都必须以 Uint8Array 的形式流动
  2. ReadableStream 的启动
    • async start(controller) 函数在流被消费时运行
    • for await (let chunk of chars()) 会暂停,等待 chars() 生成器每秒 yield 出一个 Uint8Array 数据块

# 示例二:管道与实时解码(解码器)

实际应用中,我们需要看到的是字符串,而不是一堆字节数组。这时候就需要使用 TextDecoderStream 将字节流转换为字符串流

将上面的 encodedTextStream 通过 pipeThrough 管道连接到 TextDecoderStream ,即可实现实时解码

//... (chars () 和 encodedTextStream 的定义保持不变)
// 新增:解码流
const decodedTextStream = encodedTextStream.pipeThrough(
  new TextDecoderStream()
);
// 读取解码流
const reader = decodedTextStream.getReader();
(async function () {
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    console.log("解码字符:", value);
  }
})();
  1. pipeThrough() 的力量

    • encodedTextStream.pipeThrough(new TextDecoderStream()) 创建了一个数据处理管道
    • 数据流: Uint8Array 流 -> TextDecoderStream -> String
    • TextDecoderStream 接收到上游的字节数组后,立即将其解码为字符串,并将字符串输出到新的流
  2. 解码的智能性

    • 如果数据是单字节字符(例如 ASCII 字符), TextDecoderStream 会立即将其解码为字符串
    • 如果数据是多字节字符(例如 UTF-8 编码的中文字符), TextDecoderStream 会等待足够的字节数,才能将其解码为字符串
    [102, 111, 111] -> "foo"
    [240, 159, 152, 138] -> 😀
    

# 总结

  1. 异步生成器:灵活数据源、
  2. ReadableStream :作为数据流的入口
  3. 管道:作为数据处理和格式转换的流水线
  4. TextEncoderStream / TextDecoderStream :作为字节和字符串的转换工具