60 秒回答模板

我会先说链路:前端通过 EventSource 或 fetch 读取 text/event-stream,后端按 token、句子或事件块持续推送,前端解析事件后把增量内容追加到消息状态。吐字动画不要简单每来一个 chunk 就 setState 一次,因为网络 chunk 和视觉字符不是同一节奏。更好的做法是把服务端增量放入缓冲队列,用 requestAnimationFrame 或固定节拍从队列消费字符或小片段,既能保持流畅,也能避免 React/Vue 高频渲染。还要处理 Markdown、代码块、光标、自动滚动和用户手动滚动冲突。工程上要支持取消请求、超时、错误重试、断线恢复、重复片段去重、结束标记和用量统计。回答时重点不是某个框架,而是 SSE 流解析、缓冲、渲染节流和用户体验边界。

考点 SSE 接收
难度 真实面经题
回答目标 讲清流式渲染、缓冲和交互稳定性

深入解析

01

先说明 SSE 在流式输出中的角色

SSE 适合服务端向浏览器持续推送文本事件。AI 后端可以把模型增量、状态、错误和结束信号包装成事件,前端逐步接收并更新一条正在生成的消息。它比轮询实时,比 WebSocket 简单,但主要是服务端到客户端方向。

02

网络增量和视觉吐字要解耦

模型返回的 chunk 可能忽快忽慢,甚至一个 chunk 包含多个字或半段 Markdown。如果每个 chunk 都立即渲染,页面可能抖动或卡顿。应先把增量写入缓冲区,再按稳定节奏消费,用户看到的是平滑吐字,而不是网络抖动。

03

状态结构要支持增量合并

前端消息状态通常包含 messageId、content、status、cursor、error、usage 和 abortController。收到 delta 追加内容,收到 replace 或 metadata 更新对应字段,收到 done 把状态改成完成。这样可以避免多条流互相覆盖。

04

渲染要节流并保护复杂内容

Markdown、代码块、列表和表格在流式过程中可能处于未闭合状态。可以用轻量解析、延迟完整渲染、代码块占位和 requestAnimationFrame 批量更新来降低开销。长回答还要做虚拟化或分段渲染,避免内容越长越卡。

05

交互边界决定体验质量

用户应能停止生成、重新生成、复制已生成内容、在错误后重试。自动滚动只在用户位于底部时生效,用户手动上滑阅读时不要强行拉回底部。错误状态要保留已生成内容,并提示可以继续或重试。

06

异常处理要覆盖真实网络情况

SSE 可能遇到代理缓冲、连接断开、重复事件、后端超时和浏览器标签页挂起。前端要用事件 id 或消息版本做幂等,使用 AbortController 取消请求,区分用户取消、网络错误和模型错误,并记录首 token 延迟和完整耗时。

易错点

  • 把 SSE 只说成接口框架,没有说明 text/event-stream 的增量事件处理。
  • 每收到一个 chunk 就立即渲染,导致高频更新、卡顿和吐字节奏不稳定。
  • 没有按 messageId 隔离多条生成中消息,容易出现串流或覆盖。
  • 忽略停止生成、断线、错误、超时和重复事件等真实边界。
  • 自动滚动无条件拉到底部,打断用户阅读已生成内容。
  • 没有处理 Markdown 未闭合、代码块和长文本渲染性能。

面试官追问

SSE 和 WebSocket 在这个场景怎么取舍?

如果主要是服务端向前端推送模型增量,SSE 简单且够用;如果需要强双向实时协作、客户端频繁发送控制消息,WebSocket 更合适。

吐字动画为什么不能直接按服务端 chunk 渲染?

服务端 chunk 受模型、网络和代理影响,节奏不稳定。直接渲染会忽快忽慢,还会造成高频状态更新,应该用缓冲队列平滑消费。

Markdown 流式渲染容易出什么问题?

未闭合代码块、表格和列表会让解析结果反复变化。可以分段解析、对代码块做临时态,或在生成结束后做一次完整重渲染。

用户点击停止生成后前端要做什么?

前端要 abort 当前请求,更新消息状态为已停止,保留已有内容,关闭光标动画,并允许重试或继续生成。