ChatGPT流式输出完全解析之SSE

274 阅读11分钟

最近在面试,在整理之前做过的项目,整理的过程中我会整理相关技术栈的实现,本篇是任何对话式ai应用都会遇到的流式输出协议的其中之一SSE,chatpgt用的就是这个,它是单向的,服务端向客户端推送

我用langChain做了一个案例,大家可以clone下来看下具体的实现,不过大多数的ai应用中还是会选择socket的,因为需要双向通信,但是SSE相比socket而言没那么重,写简单项目还是很方便的,比如智能对话助手什么的

2025-10-10 20.49.19.gif

项目代码Learn-LLM

📡 SSE 核心原理

1.1 什么是 SSE?

Server-Sent Events (SSE) 是 HTML5 标准的一项服务端推送技术,允许服务器主动向客户端推送数据。

核心特点

✅ 单向通信      - 服务器 → 客户端(推送)
✅ 基于 HTTP     - 无需额外协议支持
✅ 持久连接      - 长连接,持续推送
✅ 文本格式      - 简单的文本协议
✅ 自动重连      - 浏览器内置重连机制
✅ 事件驱动      - 支持自定义事件类型

技术优势

特性SSEWebSocket长轮询
通信方向单向(服务器→客户端)双向单向
协议HTTPWebSocketHTTP
自动重连✅ 内置❌ 需手动实现❌ 需手动实现
复杂度简单中等简单
适用场景推送通知、流式输出实时聊天、游戏简单轮询

🔧 SSE 实现详解

2.1 前端实现(EventSource API)

// 1. 创建 SSE 连接
const eventSource = new EventSource('/api/streaming/sse?message=hello');

// 2. 监听默认消息
eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('收到数据:', data);
};

// 3. 监听自定义事件
eventSource.addEventListener('status', (event) => {
  console.log('状态更新:', event.data);
});

eventSource.addEventListener('progress', (event) => {
  console.log('进度更新:', event.data);
});

eventSource.addEventListener('complete', (event) => {
  console.log('完成:', event.data);
  eventSource.close(); // 关闭连接
});

// 4. 连接生命周期
eventSource.onopen = () => {
  console.log('✅ SSE 连接已建立');
};

eventSource.onerror = (error) => {
  console.error('❌ SSE 错误:', error);
  // 浏览器会自动尝试重连
};

// 5. 手动关闭连接
eventSource.close();

EventSource 状态

EventSource.CONNECTING(0); // 连接中
EventSource.OPEN(1); // 已连接
EventSource.CLOSED(2); // 已关闭

// 检查状态
if (eventSource.readyState === EventSource.OPEN) {
  console.log('连接正常');
}

2.2 后端实现(Next.js)

import { NextRequest } from 'next/server';

export async function GET(request: NextRequest) {
  const encoder = new TextEncoder();

  const readable = new ReadableStream({
    async start(controller) {
      try {
        // 发送基本消息
        controller.enqueue(
          encoder.encode('data: {"content": "Hello World"}\n\n')
        );

        // 发送自定义事件
        controller.enqueue(encoder.encode('event: status\ndata: 处理中\n\n'));

        // 发送进度事件
        controller.enqueue(encoder.encode('event: progress\ndata: 50%\n\n'));

        // 发送完成事件
        controller.enqueue(
          encoder.encode('event: complete\ndata: 处理完成\n\n')
        );

        // 可选:主动关闭
        // controller.close();
      } catch (error) {
        controller.error(error);
      }
    },

    cancel(reason) {
      console.log('客户端关闭连接:', reason);
    },
  });

  return new Response(readable, {
    headers: {
      'Content-Type': 'text/event-stream', // ← SSE 的核心标识
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
      'Access-Control-Allow-Origin': '*',
    },
  });
}

2.3 SSE 数据格式规范

# 基本消息格式
data: Hello World\n\n

# 多行数据
data: 第一行\n
data: 第二行\n\n

# 自定义事件
event: notification\n
data: 新消息\n\n

# 带 ID 的消息(用于断线重连)
id: 123\n
data: 可重连的消息\n\n

# 设置重连时间(毫秒)
retry: 3000\n\n

# 重要规则:
# 1. 每个消息必须以两个换行符结尾 \n\n
# 2. data 字段可以多行
# 3. event 字段定义自定义事件类型
# 4. id 字段用于断线重连时的位置恢复

2.4 ChatGPT 流式对话实现

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const userMessage = searchParams.get('message') || 'Hello';

  const encoder = new TextEncoder();

  const readable = new ReadableStream({
    async start(controller) {
      // 初始化 LangChain ChatOpenAI
      const llm = new ChatOpenAI({
        openAIApiKey: process.env.OPEN_API_KEY,
        modelName: 'gpt-3.5-turbo',
        temperature: 0.7,
        streaming: true, // ← 开启流式输出
      });

      // 创建处理链
      const chain = chatPrompt.pipe(llm).pipe(new StringOutputParser());

      // 开始流式调用
      const stream = await chain.stream({ userMessage });

      // 逐块发送
      for await (const chunk of stream) {
        controller.enqueue(
          encoder.encode(`data: ${JSON.stringify({ content: chunk })}\n\n`)
        );
      }

      // 发送完成事件
      controller.enqueue(
        encoder.encode('event: complete\ndata: AI对话完成\n\n')
      );
    },
  });

  return new Response(readable, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
    },
  });
}

🏗️ 技术栈解析

┌─────────────────────────────────────────┐
│          SSE (Server-Sent Events)       │  ← 应用层协议(HTML5 标准)
├─────────────────────────────────────────┤
│  前端: EventSource API                   │  ← SSE 专属 API
│  格式: data:, event:, id:, retry:       │  ← SSE 专属格式
│  头部: text/event-stream                │  ← SSE 专属标识
└─────────────────────────────────────────┘
           ↓ 使用(但不专属)
┌─────────────────────────────────────────┐
│      Web Streams API (通用)             │  ← 通用流处理 API
│  - ReadableStream                       │
│  - WritableStream                       │
│  - TransformStream                      │
│  - Controller (enqueue, close, error)   │
└─────────────────────────────────────────┘
           ↓ 使用
┌─────────────────────────────────────────┐
│      Web Encoding API (通用)            │  ← 通用编码 API
│  - TextEncoder (字符串 → 字节)          │
│  - TextDecoder (字节 → 字符串)          │
└─────────────────────────────────────────┘
           ↓ 基于
┌─────────────────────────────────────────┐
│           HTTP 协议                     │  ← 传输层协议
└─────────────────────────────────────────┘

3.2 核心组件归属

技术归属用途是否 SSE 专属
EventSourceSSE API接收 SSE 消息SSE 专属
text/event-streamSSE 标准标识 SSE 响应SSE 专属
data:\n\n 格式SSE 协议SSE 消息格式SSE 专属
TextEncoderWeb Encoding API字符串转字节❌ 通用工具
ReadableStreamStreams API流式数据处理❌ 通用工具
controllerStreams API控制流的发送❌ 通用工具

3.3 为什么 SSE 需要这些 API?

🤔 核心问题:HTTP Response 只能返回字节流

// ❌ HTTP 响应不能直接发送 JavaScript 字符串
return new Response('Hello World'); // 这会被自动转换为字节

// ✅ HTTP 响应必须是字节流
return new Response(ReadableStream<Uint8Array>); // 字节流

💡 SSE 的数据流转过程

1. JavaScript 字符串
   ↓ TextEncoder(字符串 → 字节)
2. Uint8Array 字节数组
   ↓ ReadableStream(组织成流)
3. ReadableStream<Uint8Array>
   ↓ HTTP Response
4. 浏览器接收字节流
   ↓ EventSource(解析 SSE 格式)
5. JavaScript 字符串事件

📝 为什么需要 Web Encoding API?

问题:HTTP 只传输字节,JavaScript 操作字符串

// SSE 格式是字符串
const sseMessage = 'data: Hello World\n\n';

// ❌ 不能直接发送字符串
controller.enqueue(sseMessage); // 类型错误!

// ✅ 必须先转换为字节
const encoder = new TextEncoder();
controller.enqueue(encoder.encode(sseMessage)); // Uint8Array

TextEncoder 的作用:

  • 将 JavaScript 字符串编码为 UTF-8 字节数组
  • 处理多语言字符(中文、emoji 等)
  • 确保网络传输的正确性
const encoder = new TextEncoder();

// 示例:编码不同内容
encoder.encode('Hello'); // Uint8Array [72, 101, 108, 108, 111]
encoder.encode('你好'); // Uint8Array [228, 189, 160, 229, 165, 189]
encoder.encode('🎉'); // Uint8Array [240, 159, 142, 137]

🌊 为什么需要 Web Streams API?

问题 1:SSE 需要持续推送数据

// ❌ 普通响应只能一次性返回
return new Response('data: message\n\n'); // 发送后就结束了

// ✅ ReadableStream 可以持续推送
const readable = new ReadableStream({
  async start(controller) {
    // 可以多次发送
    controller.enqueue(encoder.encode('data: message1\n\n'));
    await sleep(1000);
    controller.enqueue(encoder.encode('data: message2\n\n'));
    await sleep(1000);
    controller.enqueue(encoder.encode('data: message3\n\n'));
    // 连接保持打开...
  },
});

问题 2:SSE 需要控制发送时机

// ReadableStream 的 controller 提供精确控制
const readable = new ReadableStream({
  async start(controller) {
    // 1. 立即发送初始消息
    controller.enqueue(encoder.encode('data: 开始\n\n'));

    // 2. 等待异步操作
    const result = await fetchData();

    // 3. 根据结果发送
    for await (const chunk of result) {
      controller.enqueue(encoder.encode(`data: ${chunk}\n\n`));
    }

    // 4. 发送完成事件
    controller.enqueue(encoder.encode('event: complete\ndata: 结束\n\n'));

    // 5. 可选:关闭流
    // controller.close();
  },
});

问题 3:SSE 需要处理客户端断开

const readable = new ReadableStream({
  async start(controller) {
    const interval = setInterval(() => {
      controller.enqueue(encoder.encode('data: tick\n\n'));
    }, 1000);

    // ✅ 关键:客户端断开时清理资源
    return () => {
      clearInterval(interval);
      console.log('客户端断开,已清理定时器');
    };
  },

  cancel(reason) {
    // 客户端主动关闭时触发
    console.log('客户端取消连接:', reason);
  },
});

🔄 完整的 SSE 数据流

export async function GET(request: NextRequest) {
  const encoder = new TextEncoder(); // ← 步骤1: 准备编码器

  const readable = new ReadableStream({
    // ← 步骤2: 创建流
    async start(controller) {
      // 步骤3: 准备字符串数据
      const message = 'Hello World';
      const sseFormat = `data: ${message}\n\n`;

      // 步骤4: 字符串 → 字节
      const bytes = encoder.encode(sseFormat);

      // 步骤5: 推送到流中
      controller.enqueue(bytes);

      // 步骤6: 可以继续推送...
      // controller.enqueue(encoder.encode('data: more\n\n'));
    },
  });

  // 步骤7: 包装为 HTTP 响应
  return new Response(readable, {
    headers: {
      'Content-Type': 'text/event-stream', // ← 告诉浏览器这是 SSE
    },
  });
}

// 客户端接收:
// ReadableStream<Uint8Array> → EventSource 解析 → JavaScript 事件

📊 对比:有无这些 API 的区别

场景没有这些 API使用这些 API
发送数据❌ 只能一次性返回✅ 可以持续推送
字符串处理❌ 需要手动处理编码✅ TextEncoder 自动处理
流控制❌ 无法控制发送时机✅ controller 精确控制
资源清理❌ 连接断开无通知✅ cancel() 回调清理
背压处理❌ 无法知道客户端状态✅ desiredSize 指示队列
错误处理❌ 只能返回错误响应✅ 可以在流中发送错误

🌊 Web Streams API 完整介绍

4.1 三大核心流类型

ReadableStream(可读流)📖

const readable = new ReadableStream({
  // 初始化
  start(controller) {
    controller.enqueue('数据块1');
    controller.enqueue('数据块2');
    controller.close();
  },

  // 消费者准备好接收更多数据时调用
  pull(controller) {
    // 可以延迟发送数据
  },

  // 消费者取消订阅时调用
  cancel(reason) {
    console.log('取消原因:', reason);
  },
});

// 消费方式1: Reader
const reader = readable.getReader();
const { value, done } = await reader.read();

// 消费方式2: for await...of(推荐)
for await (const chunk of readable) {
  console.log(chunk);
}

应用场景:

  • ✅ SSE 服务器推送
  • ✅ Fetch API 响应体
  • ✅ 文件读取
  • ✅ WebSocket 数据接收

WritableStream(可写流)✍️

const writable = new WritableStream({
  start(controller) {
    console.log('写入流初始化');
  },

  async write(chunk, controller) {
    console.log('接收到:', chunk);
    await saveToDatabase(chunk);
  },

  close() {
    console.log('写入完成');
  },

  abort(reason) {
    console.log('写入中止:', reason);
  },
});

// 使用方式
const writer = writable.getWriter();
await writer.write('Hello');
await writer.write('World');
await writer.close();

应用场景:

  • 文件下载保存
  • 数据持久化
  • 网络上传

TransformStream(转换流)🔄

// 创建转换流:将文本转为大写
const uppercase = new TransformStream({
  transform(chunk, controller) {
    controller.enqueue(chunk.toUpperCase());
  },
});

// 使用管道连接
readable.pipeThrough(uppercase).pipeTo(writable);

// 实际例子:压缩流
const compressor = new TransformStream({
  async transform(chunk, controller) {
    const compressed = await compress(chunk);
    controller.enqueue(compressed);
  },

  flush(controller) {
    // 处理最后的数据
  },
});

// 链式处理
fetch('/api/data')
  .then((response) => response.body)
  .pipeThrough(decompressor)
  .pipeThrough(jsonParser)
  .pipeTo(display);

应用场景:

  • 数据压缩/解压
  • 编码转换(Base64、UTF-8)
  • 数据加密/解密
  • 文本处理(过滤、替换)

4.2 核心控制器

ReadableStreamDefaultController

interface ReadableStreamDefaultController {
  enqueue(chunk: any): void; // 添加数据块到队列
  close(): void; // 关闭流
  error(error: any): void; // 发送错误
  readonly desiredSize: number; // 队列中可容纳的数据大小(背压)
}

// 使用示例
const readable = new ReadableStream({
  start(controller) {
    // 检查背压
    if (controller.desiredSize <= 0) {
      console.log('队列已满,暂停发送');
      return;
    }

    controller.enqueue('数据');

    // 发生错误
    if (error) {
      controller.error(new Error('处理失败'));
    }

    // 完成
    controller.close();
  },
});

WritableStreamDefaultController

interface WritableStreamDefaultController {
  error(error: any): void; // 发送错误信号
}

TransformStreamDefaultController

interface TransformStreamDefaultController {
  enqueue(chunk: any): void; // 输出转换后的数据
  terminate(): void; // 终止流
  readonly desiredSize: number; // 背压指示
}

4.3 读取器与写入器

ReadableStreamDefaultReader

const reader = readable.getReader();

// 读取数据
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  console.log('读取到:', value);
}

// 取消读取
await reader.cancel('用户取消');

// 释放锁(允许其他 reader)
reader.releaseLock();

WritableStreamDefaultWriter

const writer = writable.getWriter();

// 写入数据
await writer.write('数据块1');
await writer.write('数据块2');

// 关闭
await writer.close();

// 中止
await writer.abort('发生错误');

// 释放锁
writer.releaseLock();

4.4 管道操作

// 1. pipeTo - 流向可写流
await readable.pipeTo(writable);

// 2. pipeThrough - 流经转换流
const transformed = readable
  .pipeThrough(transformStream1)
  .pipeThrough(transformStream2);

// 3. 实际应用:文件处理流水线
fetch('/api/large-file')
  .then((response) => response.body) // ReadableStream
  .pipeThrough(decompressStream) // 解压
  .pipeThrough(decryptStream) // 解密
  .pipeThrough(parseStream) // 解析
  .pipeThrough(validateStream) // 验证
  .pipeTo(saveToFileStream); // 保存

// 4. 带选项的管道
await readable.pipeTo(writable, {
  preventClose: true, // 不自动关闭 writable
  preventAbort: true, // 不传播 abort
  preventCancel: true, // 不传播 cancel
  signal: abortSignal, // 取消信号
});

4.5 完整 API 对照表

API类型主要方法/属性用途
ReadableStream可读流getReader(), pipeThrough(), pipeTo()读取数据源
WritableStream可写流getWriter(), abort()写入数据
TransformStream转换流readable, writable数据转换
ReadableStreamDefaultController控制器enqueue(), close(), error()控制可读流
WritableStreamDefaultController控制器error()控制可写流
TransformStreamDefaultController控制器enqueue(), terminate()控制转换流
ReadableStreamDefaultReader读取器read(), cancel(), releaseLock()读取数据块
ReadableStreamBYOBReader字节读取器read(buffer)高效字节读取
WritableStreamDefaultWriter写入器write(), close(), abort()写入数据块

🔤 Web Encoding API 完整介绍

5.1 核心概念

Web Encoding API 提供字符串与字节之间的编码转换功能,是处理网络传输、文件操作的基础工具。

为什么需要编码?

问题:计算机只能处理数字(字节),网络也只能传输字节
解决:需要在 JavaScript 字符串 ↔ 字节数组 之间转换

JavaScript 字符串 "Hello"TextEncoder
Uint8Array [72, 101, 108, 108, 111]  ← 可以通过网络传输
    ↓ TextDecoder
JavaScript 字符串 "Hello"

5.2 TextEncoder(编码器)

基本用法

// 创建编码器(默认 UTF-8)
const encoder = new TextEncoder();

// 字符串 → 字节数组
const bytes = encoder.encode('Hello World');
console.log(bytes); // Uint8Array(11) [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]

// 查看编码格式
console.log(encoder.encoding); // "utf-8"(只读,固定为 UTF-8)

API 详解

interface TextEncoder {
  readonly encoding: string; // 固定为 "utf-8"

  // 编码整个字符串
  encode(input?: string): Uint8Array;

  // 编码到已有缓冲区(流式编码)
  encodeInto(
    source: string,
    destination: Uint8Array
  ): TextEncoderEncodeIntoResult;
}

interface TextEncoderEncodeIntoResult {
  read: number; // 已读取的源字符数
  written: number; // 已写入的字节数
}

encode() 方法

const encoder = new TextEncoder();

// 1. 基本使用
encoder.encode('Hello'); // Uint8Array [72, 101, 108, 108, 111]

// 2. 空字符串
encoder.encode(''); // Uint8Array []

// 3. 中文字符(UTF-8 编码,每个中文 3 字节)
encoder.encode('你好'); // Uint8Array [228, 189, 160, 229, 165, 189]

// 4. Emoji(UTF-8 编码,4 字节)
encoder.encode('🎉'); // Uint8Array [240, 159, 142, 137]

// 5. 混合内容
encoder.encode('Hello 你好 🎉'); // 包含所有字符的字节表示

encodeInto() 方法(流式编码)

const encoder = new TextEncoder();

// 创建目标缓冲区
const buffer = new Uint8Array(20);

// 编码到缓冲区
const result = encoder.encodeInto('Hello World', buffer);

console.log(result);
// { read: 11, written: 11 }
// read: 读取了 11 个字符
// written: 写入了 11 个字节

console.log(buffer);
// Uint8Array [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0]
//            H   e    l    l    o    空  W   o    r    l    d    ← 后面是未使用的 0

// 缓冲区不足的情况
const smallBuffer = new Uint8Array(5);
const result2 = encoder.encodeInto('Hello World', smallBuffer);
console.log(result2);
// { read: 5, written: 5 }  ← 只能写入 5 个字节
console.log(smallBuffer);
// Uint8Array [72, 101, 108, 108, 111]  ← "Hello" 的字节

实际应用场景

// ✅ SSE 数据编码
const encoder = new TextEncoder();
controller.enqueue(encoder.encode('data: Hello\n\n'));

// ✅ WebSocket 发送文本
const message = encoder.encode(JSON.stringify({ type: 'chat', content: 'Hi' }));
websocket.send(message);

// ✅ 文件写入
const fileContent = encoder.encode('File content here');
await writeFile('output.txt', fileContent);

// ✅ 计算字符串字节长度
const text = 'Hello 你好';
const byteLength = encoder.encode(text).length;
console.log(byteLength); // 14 字节(5 + 1 + 6 + 2)

5.3 TextDecoder(解码器)

基本用法

// 创建解码器(默认 UTF-8)
const decoder = new TextDecoder();

// 字节数组 → 字符串
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
const text = decoder.decode(bytes);
console.log(text); // "Hello"

// 查看解码格式
console.log(decoder.encoding); // "utf-8"

API 详解

interface TextDecoder {
  readonly encoding: string; // 编码格式(如 "utf-8")
  readonly fatal: boolean; // 是否抛出解码错误
  readonly ignoreBOM: boolean; // 是否忽略字节序标记

  // 解码字节数组
  decode(
    input?: BufferSource, // Uint8Array 或 ArrayBuffer
    options?: TextDecodeOptions // 解码选项
  ): string;
}

interface TextDecodeOptions {
  stream?: boolean; // 流式解码(保留不完整字符)
}

支持的编码格式

// UTF-8(默认,推荐)
const utf8Decoder = new TextDecoder('utf-8');

// 其他编码格式
const gbkDecoder = new TextDecoder('gbk'); // 中文 GBK
const latinDecoder = new TextDecoder('iso-8859-1'); // 拉丁文
const shiftJisDecoder = new TextDecoder('shift-jis'); // 日文

// 检查支持的编码
try {
  new TextDecoder('unknown-encoding');
} catch (error) {
  console.error('不支持的编码格式');
}

decode() 方法

const decoder = new TextDecoder();

// 1. 基本解码
const bytes1 = new Uint8Array([72, 101, 108, 108, 111]);
decoder.decode(bytes1); // "Hello"

// 2. 解码中文
const bytes2 = new Uint8Array([228, 189, 160, 229, 165, 189]);
decoder.decode(bytes2); // "你好"

// 3. 解码 Emoji
const bytes3 = new Uint8Array([240, 159, 142, 137]);
decoder.decode(bytes3); // "🎉"

// 4. 空字节
decoder.decode(new Uint8Array([])); // ""

// 5. 使用 ArrayBuffer
const buffer = new ArrayBuffer(5);
const view = new Uint8Array(buffer);
view.set([72, 101, 108, 108, 111]);
decoder.decode(buffer); // "Hello"

流式解码

const decoder = new TextDecoder();

// 问题:多字节字符可能被分割
const part1 = new Uint8Array([228, 189]); // "你" 的前 2 字节
const part2 = new Uint8Array([160]); // "你" 的最后 1 字节

// ❌ 不使用 stream 选项
decoder.decode(part1); // "�" (乱码,字符不完整)
decoder.decode(part2); // "�" (乱码)

// ✅ 使用 stream 选项
const streamDecoder = new TextDecoder();
const text1 = streamDecoder.decode(part1, { stream: true }); // ""(等待更多字节)
const text2 = streamDecoder.decode(part2, { stream: false }); // "你"(完成解码)
console.log(text1 + text2); // "你"

错误处理

// 1. fatal 模式(遇到无效字节抛出错误)
const fatalDecoder = new TextDecoder('utf-8', { fatal: true });

try {
  const invalidBytes = new Uint8Array([0xff, 0xfe]); // 无效的 UTF-8
  fatalDecoder.decode(invalidBytes);
} catch (error) {
  console.error('解码失败:', error); // EncodingError
}

// 2. 非 fatal 模式(替换为 � )
const decoder = new TextDecoder('utf-8', { fatal: false });
const invalidBytes = new Uint8Array([0xff, 0xfe]);
decoder.decode(invalidBytes); // "��"(替换字符)

5.4 完整 API 对照表

API方法/属性参数返回值用途
TextEncoderconstructor()-TextEncoder创建 UTF-8 编码器
encode(str)stringUint8Array字符串 → 字节数组
encodeInto(str, buffer)string, Uint8Array{read, written}编码到已有缓冲区
encoding-"utf-8"编码格式(只读)
TextDecoderconstructor(label?, options?)string, {fatal?, ignoreBOM?}TextDecoder创建解码器
decode(buffer?, options?)BufferSource, {stream?}string字节数组 → 字符串
encoding-string编码格式(只读)
fatal-boolean是否抛出错误(只读)
ignoreBOM-boolean是否忽略 BOM(只读)

5.5 总结

Web Encoding API 核心要点:

  1. TextEncoder:字符串 → 字节(仅支持 UTF-8)
  2. TextDecoder:字节 → 字符串(支持多种编码)
  3. 在 SSE 中的作用
    • 将 SSE 格式字符串编码为字节
    • 使 HTTP Response 能够传输文本数据
  4. 性能优化
    • 复用编码器/解码器实例
    • 大量编码时使用 encodeInto()
    • 流式解码时使用 { stream: true }