优先用 EventSource 实现标准 SSE 的 AI 聊天框,因其自动解析、重连、轻量;若需自定义请求头、POST 请求或裸文本流,则必须用 Fetch + ReadableStream。

用 Fetch + ReadableStream 实现流式响应的 AI 聊天框
Fetch 返回的 Response.body 是一个 ReadableStream,适合处理服务端分块返回(如 chunked transfer encoding)的 AI 流式输出。它比 EventSource 更通用,支持任意格式(JSON、纯文本、自定义分隔符),也更容易控制请求头和认证。
关键点:需手动解析流、按 chunk 拆分、累积并渲染;不能自动重连;需自己处理错误和中断。
- 发送请求时设置
headers: { 'Accept': 'text/event-stream' }或纯text/plain,取决于后端返回格式 - 用
response.body.getReader()获取 reader,循环调用read()获取Uint8Array - 把二进制数据转成字符串(
new TextDecoder().decode(chunk)),再按换行或特定分隔符(如\n、data:前缀)拆解 - 每拿到一段有效内容(例如一个 token 或 JSON 对象),立即更新聊天消息 DOM,避免等待整个响应结束
- 注意处理流中断(
done === true)、网络错误、abort controller 主动取消
用 EventSource 实现标准 SSE 的 AI 聊天框
EventSource 是浏览器原生的 Server-Sent Events 客户端,专为服务端推送设计。AI 接口若按 SSE 协议返回(每行以 data: 开头、双换行分隔),它会自动解析、重连、保持连接,开发更轻量。
限制:只支持 GET 请求;无法自定义请求头(如 Authorization 需用 cookie 或 URL 参数);只能接收 text/event-stream 类型响应。
立即学习“Java免费学习笔记(深入)”;
- 创建实例:
const es = new EventSource('/api/chat', { withCredentials: true }) - 监听
message事件获取默认事件,或监听自定义事件名(如es.addEventListener('token', ...)) - SSE 数据格式示例:
data: {"delta":"hello"}\n\n→ 解析后event.data是字符串,需JSON.parse(event.data) - 连接断开时浏览器自动重试(可配置
retry:字段),也可手动调用es.close() - 不支持 POST,如需传 prompt,得拼在 URL 里或改用 Fetch + Stream
两种方式怎么选?看后端输出和需求
如果后端提供标准 SSE 接口(如 FastAPI 的 StreamingResponse + Content-Type: text/event-stream),优先用 EventSource —— 简单、健壮、少出错。
如果后端返回的是裸文本流(如 Express 中 res.write('token1\n') → res.write('token2\n'))、需要带 Authorization header、或要 POST 大量上下文,则必须用 Fetch + ReadableStream。
- 调试时可在 DevTools Network 查看响应的 Content-Type 和实际数据格式,确认是
text/event-stream还是text/plain - 移动端或低版本 Safari 不支持
EventSource(可降级为轮询或改用 Fetch) - 需要取消请求、超时控制、上传进度等高级能力,Fetch 更灵活
流式渲染要注意的细节
无论哪种方式,前端都要避免“整段刷新”,而是增量追加内容。常见做法是给当前 AI 消息容器一个临时 ID,每次收到新 token 就 element.textContent += token 或用 textContent + innerHTML 混合处理 Markdown。
- 防重复渲染:确保每个 chunk 只追加一次,不要在
onmessage或read()中重复操作 DOM - 光标跟随:滚动到底部用
chatContainer.scrollTop = chatContainer.scrollHeight - 空格与换行:SSE/Fetch 返回的 token 可能含前导空格或 \n,建议 trim 后再拼接,或用
white-space: pre-wrapCSS 保留合理换行 - 错误状态反馈:监听
error事件或 catchfetch().then().catch(),显示友好提示而非白屏


















