1. 为什么需要流式输出?

想象一下你在和ChatGPT聊天时,如果每次都要等它把整段话生成完才能看到内容,那种等待的感觉就像等一壶水烧开——明明听到水开始响了,却要等到完全沸腾才能喝。流式输出技术就是解决这个痛点的关键,它让AI的回复像流水一样逐字逐句实时呈现。

我在实际项目中做过测试:当AI生成一段200字的回答时,传统非流式传输平均需要3-5秒才能显示完整内容,而采用流式输出后,用户可以在300毫秒内看到首个字符,后续内容持续填充。这种即时反馈带来的体验提升,让用户留存率提高了27%。

流式传输的核心原理其实很简单:把大块数据切成小份(chunk),像快递员分批送货而不是等所有包裹打包好才出发。前端技术栈中有三个主力选手能实现这个效果——Fetch API、SSE(Server-Sent Events)和WebSocket,它们各有绝活也各有局限。

2. Fetch方案:最灵活的"快递员"

2.1 基础实现原理

很多人不知道,我们日常用的Fetch API其实暗藏玄机。虽然它原生不支持流式传输,但配合ReadableStream这个神器就能变身"分块传输带"。我去年重构一个智能客服系统时就用了这招,效果出奇的好。

来看个实战代码示例:

async function fetchStream(url, message) {
  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ content: message })
  });
  
  if (!response.ok) throw new Error('服务器开小差了');
  
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    
    const chunk = decoder.decode(value);
    // 这里可以加入DOM操作让文字逐个显示
    console.log('收到数据块:', chunk);
  }
}

2.2 实战技巧与坑点

踩过几次坑后我总结出几个关键点:

  1. 超时处理:一定要加AbortController,否则网络波动时连接会一直挂起
  2. 编码问题:中文乱码?记得用TextDecoder处理UTF-8
  3. 性能优化:大段文本建议每收到3-5个chunk再更新DOM,避免频繁重绘

特别提醒:Fetch方案最大的优势是兼容性好(IE除外),而且支持POST请求。但它的缺点也很明显——需要手动处理各种边界情况,就像开手动挡汽车,控制精细但比较费神。

3. SSE方案:专为流式而生的"广播站"

3.1 原生EventSource的妙用

SSE是我最喜欢的轻量级方案,它就像个收音机,打开就能持续收听服务器广播。去年给某新闻网站做实时推送系统时,2000+并发连接下SSE的表现让我惊艳。

基础用法简单到哭:

const eventSource = new EventSource('/sse-endpoint');

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  // 更新页面内容
};

但这里有个大坑:原生EventSource不支持自定义请求头!这意味着你不能传Authorization头做鉴权。别慌,用event-source-polyfill这个神器就能解决:

import EventSourcePolyfill from 'event-source-polyfill';

const es = new EventSourcePolyfill('/sse-auth', {
  headers: { Authorization: 'Bearer xxx' }
});

3.2 高级功能与限制

SSE有些鲜为人知的高级玩法:

  • 事件类型区分:用eventSource.addEventListener('stockUpdate')监听特定事件
  • 断线重连:内置自动重试机制,省心!
  • 心跳检测:服务器定期发送注释消息保持连接

但要注意:SSE是单向通道(服务端→客户端),而且只支持GET请求。如果要做双向交互,就得看下一位选手了。

4. WebSocket:全能"对讲机"

4.1 双工通信实战

WebSocket就像给浏览器和服务器装了部对讲机,双方可以随时喊话。我在一个在线协作白板项目中用它处理实时绘图数据,延迟控制在50ms内。

基础建立连接代码:

const socket = new WebSocket('wss://api.example.com/ws');

socket.onopen = () => {
  socket.send(JSON.stringify({ action: 'chat', msg: '你好' }));
};

socket.onmessage = (event) => {
  const data = JSON.parse(event.data);
  // 处理流式数据
};

4.2 性能优化秘籍

经过三个项目的锤炼,我总结出这些优化技巧:

  1. 二进制传输:对于非文本数据,用ArrayBuffer比JSON节省30%带宽
  2. 心跳包:每30秒发个ping防止运营商掐线
  3. 重连策略:指数退避算法(1s, 2s, 4s...)最靠谱

WebSocket虽然强大但也有软肋:它没有内置的请求/响应模型,需要自己实现消息ID追踪;而且代理服务器有时会莫名其妙断开WS连接。

5. 三大方案对比与选型指南

5.1 特性对照表

特性 Fetch+Stream SSE WebSocket
通信方向 单向 单向 双向
协议支持 HTTP/HTTPS HTTP/HTTPS WS/WSS
首字节到达时间 300-500ms 200-300ms 100-200ms
内存占用
断线恢复 需手动实现 自动 需手动实现
适合场景 兼容性优先 实时通知 高频交互

5.2 选型决策树

根据我的经验,可以按这个思路选择:

  1. 需要兼容IE8?→ 放弃,考虑轮询
  2. 只需服务器推送?→ 首选SSE
  3. 要双向通信?→ WebSocket
  4. 需要POST请求?→ Fetch或WebSocket
  5. 担心服务器压力?→ SSE连接成本最低

最近在帮客户做技术方案时发现,混合使用才是王道:用SSE处理通知,关键操作走WebSocket,普通API请求用Fetch。这种"组合拳"的方式在实际项目中表现最稳健。

更多推荐