对接DeepSeek前端不使用SSE流式输出方案
文章类型:Vue
发布者:hp
发布时间:2026-07-02
Fetch API + ReadableStream:使用原生 Fetch API 的 res读取响应流
// 关键代码片段
const response = await fetch(AI_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question: prompt })
})
const reader = response.body.getReader() // 获取流读取器
const decoder = new TextDecoder('utf-8') // 文本解码器
工作原理:
前端通过 API 持续读取数据块
后端返回的数据格式类似:
data: {"data":{"reasoning":"思考内容..."}}
data: {"data":{"content":"回答片段..."}}
data: {"data":{"done":true}}
三步解析流程:
const normalizeChunk = (raw: string) => {
return raw
.split(/\r?\n/)
.map((line) => {
const dataMatch = line.match(/^\s*data:\s*(.*)$/i)
const content = dataMatch ? dataMatch[1] : line
const braceIndex = content.indexOf('{')
return braceIndex >= 0 ? content.slice(braceIndex) : ''
})
.join('')
}
移除 前缀
const extractPayloads = (buffer: string) => {
// 通过括号配对找到完整的 JSON 对象
// 处理字符串转义、嵌套等边界情况
// 返回完整的 JSON 数组和未完成的残留数据
}
for (const payloadText of payloads) {
const payload = JSON.parse(payloadText)
// 处理思考过程(reasoning 字段)
if (payload?.data?.reasoning) {
enqueueThinkingTyping(botMessage, payload.data.reasoning)
}
// 处理回答内容(content 字段)
if (payload?.data?.content) {
enqueueTyping(botMessage, payload.data.content)
}
// 处理结束标志
if (payload?.data?.done) {
shouldStop = true
}
}
使用双队列系统:
:思考过程的待显示文本
:回答内容的待显示文本
核心机制:
const typewriterStep = () => {
// 1. 优先处理思考过程
if (thinkingTypewriterQueue.size > 0) {
// 每次显示 2 个字符,速度 50ms
const chunkSize = Math.min(2, Math.ceil(pendingText.length / 20) || 1)
message.thinking += chunk
}
// 2. 思考完成后处理回答内容
else if (typewriterQueue.size > 0) {
message.content += chunk
}
// 3. 递归调用,持续显示
setTimeout(typewriterStep, 50)
}
标准 SSE 需要:
Content-Type: text/event-streamevent: message\ndata: {...}\n\n浏览器 API
这个实现:
优点:
创新点:
用户输入问题
↓
Fetch API 发送请求
↓
ReadableStream 持续读取数据块
↓
normalizeChunk 标准化
↓
extractPayloads 提取完整 JSON
↓
解析 reasoning/content/done 字段
↓
入队到打字机队列
↓
定时器逐字显示(50ms 间隔)
↓
完成后保存对话记录
这个实现本质上是 手动实现了类似 SSE 的流式处理,但更加定制化和灵活,特别适合需要自定义协议格式的场景。
暂无评论,快来发表第一条评论吧~