流式数据处理方案

5 阅读4分钟

🌐 一、流式数据的本质

流式 = 数据分块到达 + 客户端逐块处理

  • 服务器不等全部生成完就发送数据(chunked transfer)
  • 客户端边收边处理,避免等待和内存爆炸
  • 常见于:AI 对话、日志监控、实时通知、大文件下载/上传

📦 二、前端流式处理的完整方案矩阵

方案协议/技术是否原生支持 POST自定义 Headers流式读取适用场景
1. fetch + ReadableStreamHTTP chunked✅✅✅通用流式(OpenAI 风格)
2. @microsoft/fetch-event-sourceSSE (text/event-stream)❌(需安装)✅✅SSE 接口(LangChain/Ollama)
3. 原生 EventSourceSSE❌(仅 GET)简单同域 SSE(无认证)
4. WebSocketWebSocketN/A❌(握手后无 headers)双向实时通信(聊天、协作)
5. <video> / Media Source Extensions (MSE)HLS/DASH/自定义流视频/音频流播放
6. File API + File.slice()本地文件N/AN/A大文件分片读取/上传
7. Web Transport (实验性)QUIC/HTTP/3⚠️(Chrome 97+)未来高性能流(游戏、VR)

🔍 三、详细方案解析


✅ 方案 1:fetch + ReadableStream(最通用)

适用后端:

  • OpenAI 官方 API
  • 自定义 chunked 文本流(非 SSE 格式)
  • 返回纯文本或 JSONL(JSON Lines)

示例代码:

const res = await fetch('/api/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ prompt: 'Hello' })
});

const reader = res.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const chunk = decoder.decode(value, { stream: true });
  updateUI(chunk); // 实时渲染
}

✅ 优点:

  • 完全控制流
  • 支持任意格式
  • 现代浏览器广泛支持

❌ 缺点:

  • 需手动解析格式(如 JSONL)
  • 无内置重连机制

✅ 方案 2:@microsoft/fetch-event-source(SSE 最佳实践)

适用后端:

  • FastAPI with StreamingResponse
  • LangChain / LlamaIndex
  • Ollama /api/generate
  • 自研 SSE 接口

示例代码:

import { fetchEventSource } from '@microsoft/fetch-event-source';

await fetchEventSource('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ message: 'Hi' }),
  headers: { Authorization: 'Bearer xxx' },
  onmessage(event) {
    if (event.data !== '[DONE]') {
      const data = JSON.parse(event.data);
      appendToken(data.token);
    }
  }
});

✅ 优点:

  • 自动解析 SSE 格式
  • 支持 POST + headers
  • 内置重试 + AbortController

❌ 缺点:

  • 需安装依赖
  • 仅适用于标准 SSE 格式

⚠️ 方案 3:原生 EventSource(受限但简单)

适用场景:

  • 同域 GET 请求
  • 仅需 Cookie 认证
  • 简单实时通知

示例:

const es = new EventSource('/events?userId=123');
es.onmessage = (e) => console.log(e.data);

❌ 限制:

  • 无法传 Token
  • 无法 POST
  • 跨域需 CORS 配合

✅ 方案 4:WebSocket(双向流)

适用场景:

  • 聊天应用
  • 在线协作(如 Google Docs)
  • 实时游戏

示例:

const ws = new WebSocket('wss://example.com/stream');
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  renderMessage(data);
};

✅ 优点:

  • 全双工通信
  • 低延迟
  • 支持二进制

❌ 缺点:

  • 需维护长连接
  • 服务端复杂度高

✅ 方案 5:Media Source Extensions (MSE)

适用场景:

  • 自定义视频流(如监控)
  • 动态拼接媒体片段

示例:

const video = document.querySelector('video');
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);

mediaSource.addEventListener('sourceopen', () => {
  const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E"');
  // 通过 fetch 获取视频 chunk 并 append
});

⚠️ 注意:仅用于音视频,非通用文本流。


✅ 方案 6:File API(本地流)

适用场景:

  • 用户上传大文件(分片)
  • 本地文件预览(如 CSV 分块解析)

示例:

input.onchange = (e) => {
  const file = e.target.files[0];
  const chunkSize = 1024 * 64;
  for (let start = 0; start < file.size; start += chunkSize) {
    const chunk = file.slice(start, start + chunkSize);
    const text = await chunk.text(); // 或 arrayBuffer()
    processChunk(text);
  }
};

🧪 方案 7:WebTransport(未来方向)

  • 基于 QUIC,比 WebSocket 更快
  • 支持可靠/不可靠流
  • 目前仅 Chrome 支持,暂不推荐生产使用

🧩 四、如何选择?决策树

graph TD
  A[需要流式读取?] -->|否| B[用 axios / fetch 常规请求]
  A -->|是| C{后端返回什么格式?}
  C -->|SSE text/event-stream| D{是否需要 POST 或 Token?}
  D -->|是| E[用 @microsoft/fetch-event-source]
  D -->|否| F[用原生 EventSource]
  C -->|普通 chunked 流| G[用 fetch + ReadableStream]
  C -->|双向通信| H[用 WebSocket]
  C -->|音视频| I[用 MSE 或 <video>]
  C -->|本地文件| J[用 File.slice()]

🛡️ 五、通用最佳实践(所有方案适用)

1. 内存安全

  • 限制历史消息数量(如只保留最后 50 条)
  • 使用虚拟滚动(react-window
  • 及时清理资源(AbortController / close()

2. 用户体验

  • 打字机效果(节流更新,避免卡顿)
  • 加载状态提示(“正在思考...”)
  • 支持“停止生成”按钮

3. 错误处理

  • 网络中断自动重试(SSE)
  • 优雅降级(如转为轮询)
  • 用户友好错误提示

4. 性能优化

  • 避免高频 setState(累积 buffer)
  • 使用 requestIdleCallbacksetTimeout(0) 让出主线程

📚 六、典型后端对应关系

后端技术推荐前端方案
OpenAI APIfetch + ReadableStream
Ollama / LM Studiofetch-event-source
FastAPI StreamingResponsefetch-event-source(若返回 SSE)或 fetch(若纯文本)
Spring Boot SseEmitterfetch-event-source
Node.js res.write()fetch(纯文本)或 fetch-event-source(若格式为 SSE)
WebSocket 服务原生 WebSocket

✅ 总结

场景首选方案
AI 流式对话(OpenAI 风格)fetch + ReadableStream
AI 流式对话(SSE 格式)@microsoft/fetch-event-source
简单实时通知(同域)原生 EventSource
双向实时交互WebSocket
大文件处理File.slice()
视频流MSE<video>

💡 记住

  • 浏览器中 axios 不支持流式读取!
  • 流不会自动渲染,必须主动消费!
  • 无限累积 = 内存泄漏,务必做限制!

如果你告诉我你的具体场景(比如“我用 FastAPI 返回 SSE,前端怎么接?”),我可以给出完整可运行代码