SSE#
SSE(Server-Sent Events) 是一种浏览器原生支持的、单向的流式通信协议:
服务器 → 客户端 持续不断推送数据(比如:消息、token、进度)
它基于 HTTP 协议,使用 MIME 类型 text/event-stream。
核心特性
- 单向通信:只能从服务端推送到客户端(不能反向)
- 基于 HTTP/1.x:无需额外协议,浏览器天然支持
- 保持连接、实时推送:服务端主动发送数据,客户端自动接收
使用场景
- 流式大模型输出(如 ChatGPT/Ollama)
- 实时日志、监控信息推送
- 股票/天气/订单状态实时更新
- 打字机式的问答机器人
流式处理#
在流式请求中(如 Ollama、OpenAI、Claude 的 stream: true 模式),响应体不是一次性完整返回,而是按块分批返回。
比如你请求了大模型回答问题,它不会一下子返回整段文字,而是:
'你''好'',''请问''需要''帮助吗?'这些字符被拆成若干个“chunk”,逐个返回。
假设我们开启 stream 流式模式:
const response = await fetch('/api/generate', { method: 'POST', body: JSON.stringify({ prompt: '你是谁?', stream: true }),})response.body 就是 ReadableStream 可读流,可读流有一个 getReader 的方法,可以拿到一个 Reader 对象。
const reader = response.body.getReader()该 Reader 对象上面有一个 read 方法,可以读取每一个 chunk。
while (true) { // 不断的去读每一个块,取出当前块所对应的数据 const { done, value } = await reader.read() if (done) break // 进入该 if,说明没有数据了}value: 一个Uint8Array,代表当前的字节数据块。done: 是否已经读完(true表示所有数据已读取完毕)。
但现在读取到的是二进制数据,我们需要去解码,因此我们创建一个 UTF-8 解码器,将从 reader.read() 拿到的 二进制 Uint8Array 解码为字符串。
const decoder = new TextDecoder('utf-8')TextDecoder 会自动处理中文、emoji 这类多字节字符。
解码代码如下:
const chunk = decoder.decode(value, { stream: true })例如其中一个 value 解码后结果是:
chunk = '{"response":"你好"}\n{"response":","}\n'这里设置 stream: true 的作用是:保留未完整字符到下一次解码继续拼接。
比如如果只读到 {"response":"你,它不会报错,而是等待下次补上 好"} 再组合起来。
假设将 stream 设置为 false,遇到以下情况会出 bug:
Uint8Array.from([ 123, 34, 114, 101, 115, 112, 111, 110, 115, 101, 34, 58, 34, 228,])这是部分字符 "你" 的 utf-8,但还没完整;stream: false 会报错(因为字符不完整);stream: true 会“记住”这个未完整字符,等下一次拼接回来。
-EOF-