ChatGPT流式输出完全解析之SSE

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

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

项目代码Learn-LLM

📡 SSE 核心原理

1.1 什么是 SSE?

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

核心特点

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

技术优势

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

🔧 SSE 实现详解

2.1 前端实现(EventSource API)

typescript 复制代码
// 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 状态

typescript 复制代码
EventSource.CONNECTING(0); // 连接中
EventSource.OPEN(1); // 已连接
EventSource.CLOSED(2); // 已关闭

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

2.2 后端实现(Next.js)

typescript 复制代码
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 数据格式规范

yaml 复制代码
# 基本消息格式
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 流式对话实现

typescript 复制代码
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',
    },
  });
}

🏗️ 技术栈解析

vbnet 复制代码
┌─────────────────────────────────────────┐
│          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 专属
EventSource SSE API 接收 SSE 消息 SSE 专属
text/event-stream SSE 标准 标识 SSE 响应 SSE 专属
data:\n\n 格式 SSE 协议 SSE 消息格式 SSE 专属
TextEncoder Web Encoding API 字符串转字节 ❌ 通用工具
ReadableStream Streams API 流式数据处理 ❌ 通用工具
controller Streams API 控制流的发送 ❌ 通用工具

3.3 为什么 SSE 需要这些 API?

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

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

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

💡 SSE 的数据流转过程

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

📝 为什么需要 Web Encoding API?

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

typescript 复制代码
// 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 等)
  • 确保网络传输的正确性
typescript 复制代码
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 需要持续推送数据

typescript 复制代码
// ❌ 普通响应只能一次性返回
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 需要控制发送时机

typescript 复制代码
// 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 需要处理客户端断开

typescript 复制代码
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 数据流

typescript 复制代码
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(可读流)📖

typescript 复制代码
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(可写流)✍️

typescript 复制代码
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(转换流)🔄

typescript 复制代码
// 创建转换流:将文本转为大写
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

typescript 复制代码
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

typescript 复制代码
interface WritableStreamDefaultController {
  error(error: any): void; // 发送错误信号
}

TransformStreamDefaultController

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

4.3 读取器与写入器

ReadableStreamDefaultReader

typescript 复制代码
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

typescript 复制代码
const writer = writable.getWriter();

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

// 关闭
await writer.close();

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

// 释放锁
writer.releaseLock();

4.4 管道操作

typescript 复制代码
// 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 字符串 ↔ 字节数组 之间转换

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

5.2 TextEncoder(编码器)

基本用法

typescript 复制代码
// 创建编码器(默认 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 详解

typescript 复制代码
interface TextEncoder {
  readonly encoding: string; // 固定为 "utf-8"

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

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

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

encode() 方法

typescript 复制代码
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() 方法(流式编码)

typescript 复制代码
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" 的字节

实际应用场景

typescript 复制代码
// ✅ 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(解码器)

基本用法

typescript 复制代码
// 创建解码器(默认 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 详解

typescript 复制代码
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; // 流式解码(保留不完整字符)
}

支持的编码格式

typescript 复制代码
// 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() 方法

typescript 复制代码
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"

流式解码

typescript 复制代码
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); // "你"

错误处理

typescript 复制代码
// 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 方法/属性 参数 返回值 用途
TextEncoder constructor() - TextEncoder 创建 UTF-8 编码器
encode(str) string Uint8Array 字符串 → 字节数组
encodeInto(str, buffer) string, Uint8Array {read, written} 编码到已有缓冲区
encoding - "utf-8" 编码格式(只读)
TextDecoder constructor(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 }
相关推荐
又是忙碌的一天4 小时前
前端学习 JavaScript(2)
前端·javascript·学习
2501_915106324 小时前
JavaScript编程工具有哪些?老前端的实用工具清单与经验分享
开发语言·前端·javascript·ios·小程序·uni-app·iphone
GISer_Jing4 小时前
计算机基础——浏览器、算法、计算机原理和编译原理等
前端·javascript·面试
我的xiaodoujiao4 小时前
从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 8--基础知识 4--常用函数 2
前端·python·测试工具·ui
蓝瑟4 小时前
React 项目实现拖拽排序功能,如何在众多库中选对 “它”
前端·javascript·react.js
ml魔力信息5 小时前
活体检测与防伪技术的安全与隐私分析
大数据·人工智能·安全·隐私保护·生物识别·活体检测
赴3355 小时前
基于pth模型文件,使用flask库将服务端部署到开发者电脑
人工智能·flask·客户端·模型部署·服务端
hhhhhlt5 小时前
【代码大模型-后门安全】Backdoors in Neural Models of Source Code
人工智能·安全
智驱力人工智能5 小时前
工厂抽烟检测系统 智能化安全管控新方案 加油站吸烟检测技术 吸烟行为智能监测
人工智能·算法·安全·边缘计算·抽烟检测算法·工厂抽烟检测系统·吸烟监测