news 2026/6/18 11:34:22

前端 SSE 流式响应处理实践:从接收、解析到渲染

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端 SSE 流式响应处理实践:从接收、解析到渲染

Server-Sent Events(SSE)是实现 AI 流式输出的最轻量方案。相比 WebSocket,它单向、基于 HTTP、浏览器原生支持,不需要额外库。

本文分享在"领航英语"项目中用 SSE 实现 AI 单词精讲的完整实践,包括前端接收、中断、结构化解析和逐行渲染。


为什么选 SSE

方案适用场景复杂度
轮询低频更新
SSE服务端单向推送流式数据
WebSocket双向实时通信

AI 文本生成是典型的单向流式场景:用户发请求,AI 逐 token 返回。SSE 完美匹配。


后端接口(Go + Gin)

Go 侧设置关键响应头:

func(h*AIService)ExplainVocabStream(c*gin.Context){c.Header("Content-Type","text/event-stream")c.Header("Cache-Control","no-cache")c.Header("Connection","keep-alive")c.Header("X-Accel-Buffering","no")// 禁用 Nginx 缓冲c.Stream(func(w io.Writer)bool{// 调用 LLM streaming APIstream:=llmClient.CreateChatCompletionStream(ctx,request)forchunk:=rangestream{fmt.Fprintf(w,"data: %s\n\n",chunk)c.Writer.Flush()}fmt.Fprintf(w,"data: [DONE]\n\n")returnfalse})}

关键细节:X-Accel-Buffering: no告诉 Nginx 不要缓冲这个响应。没这行的话,Nginx 会把所有 chunk 攒到一起再发给客户端——流式变一次性,效果全没了。


前端 Fetch + ReadableStream

前端不用 EventSource API(因为它不支持 POST 请求和自定义 headers),用fetch+ReadableStream

asyncfunctionstreamAIExplain(word:string,onChunk:(text:string)=>void){constresponse=awaitfetch('/api/ai/explain-vocab/stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({word})})constreader=response.body!.getReader()constdecoder=newTextDecoder()letbuffer=''while(true){const{done,value}=awaitreader.read()if(done)breakbuffer+=decoder.decode(value,{stream:true})constlines=buffer.split('\n')buffer=lines.pop()||''// 最后一部分可能不完整,留着下次拼for(constlineoflines){if(line.startsWith('data: ')){constdata=line.slice(6)if(data==='[DONE]')returnonChunk(data)}}}}

要点:

  • TextDecoderstream: true参数处理多字节字符被截断的情况(UTF-8 中一个中文字 3 字节,流式传输可能从中断开)
  • buffer 机制保证不完整的行不会丢
  • [DONE]信号标记流结束

中断请求

用户关掉 AI 精讲面板时,需要立即中断请求,否则浪费 token 和带宽:

constabortController=ref<AbortController|null>(null)conststartStream=async(word:string)=>{abortController.value=newAbortController()constresponse=awaitfetch('/api/ai/explain-vocab/stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({word}),signal:abortController.value.signal})// ...}conststopStream=()=>{abortController.value?.abort()}

AbortController是浏览器原生 API,fetch 收到 abort 信号会抛出AbortError,后端也会收到连接断开通知。


结构化 JSON 解析

AI 返回的不是纯文本,而是结构化 JSON:

{"memoryTip":"词根 spect 表示"",pro- 表示"向前",合起来就是向前看→前景","usage":"prospect of doing sth, in prospect","example":"The prospect of studying abroad excites her.","examNote":"注意与 perspective(视角)区分,考试中常混在一起出题"}

问题是:SSE 的每个 chunk 可能在任何位置断开,而 JSON 必须完整才能解析。

解法——逐 chunk 累积,尝试解析,失败就继续收

letjsonBuffer=''onChunk=(chunk:string)=>{jsonBuffer+=chunkletparsed:VocabExplain|null=nulltry{parsed=JSON.parse(jsonBuffer)}catch{return// JSON 还不完整,继续等}// 解析成功,按字段渲染renderField('memoryTip',parsed.memoryTip)renderField('usage',parsed.usage)renderField('example',parsed.example)renderField('examNote',parsed.examNote)}

生产环境建议用更鲁棒的策略:要求 LLM 逐字段输出,每个字段标记分隔符,避免等整个 JSON 收完才开始渲染。


逐行打字效果渲染

收到字段内容后,用requestAnimationFrame实现逐字渲染:

constdisplayText=ref('')letdisplayTimer:number|null=nullfunctionanimateText(fullText:string){letindex=0constspeed=30// ms per charconsttick=()=>{if(index<fullText.length){displayText.value=fullText.slice(0,++index)displayTimer=window.requestAnimationFrame(()=>setTimeout(tick,speed))}}tick()}

requestAnimationFrame保证渲染与屏幕刷新同步,不会出现卡顿和闪烁。


完整流程

用户点击 AI 精讲 → fetch POST /api/ai/explain-vocab/stream → Go 服务调用 LLM streaming API → SSE chunk 逐条返回 → 前端累积 JSON buffer → 解析成功后逐字渲染 → 用户关闭面板 → AbortController 中断

这些代码来自领航英语的实际实现。在线体验:m.dobell.top,点击任意单词卡片即可看到流式 AI 精讲效果。注册送 3 天会员,月卡 29 元。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/6 8:58:41

C++之模板(初级)

1 模板的出现来源1 泛型编程我们先思考一个问题如何实现一个通用的交换函数呢?int Swap(int& a,int& b ) {int tempa;ab;btemp; }double Swap(double& a,double& b ) {doubletempa;ab;btemp; }........你会发现要是用之前函数重载的方式是可以实现的。但是却会…

作者头像 李华
网站建设 2026/6/6 8:56:22

比起会写 Prompt,未来更值钱的是定义目标的能力

过去两年&#xff0c;AI 圈有一个特别有意思的现象。刚开始大家都在研究 Prompt&#xff0c;各种“神级提示词”、“万能模板”、“一句话让 AI 效果提升 10 倍”的内容层出不穷。那时候很多人觉得&#xff0c;谁更会写 Prompt&#xff0c;谁就更懂 AI。但最近半年&#xff0c;…

作者头像 李华
网站建设 2026/6/6 8:56:18

ESP8266+STM32获取网络时间的两种方法对比:HTTP API vs NTP协议

ESP8266STM32网络授时方案深度对比&#xff1a;HTTP API与NTP协议实战解析在物联网设备开发中&#xff0c;精确的时间同步往往是功能实现的基础需求。无论是智能家居中的定时场景&#xff0c;还是工业环境下的数据采集&#xff0c;准确的时间戳都至关重要。对于STM32ESP8266这类…

作者头像 李华
网站建设 2026/6/6 8:53:15

肯德基在线点餐API接口

请求参数说明store_idstring是1店铺IDstore_codestringBAI001门店ID。外送模式不用传&#xff0c;会按经纬度匹配门店lng收货地址经度&#xff0c;外送模式必传lat收货地址纬度&#xff0c;外送模式必传order_typenumber否配送方式&#xff1a;1到店自取2外卖

作者头像 李华