要实现对 流式返回的 JSON 数据(如 DeepSeek 的 SSE 流式响应)进行 逐字解析并实时渲染,而不是等待整个 JSON 完整加载后再处理,需要明确以下目标和问题:
文章目录
核心目标
- 逐步接收数据流(chunk by chunk)
- 逐步拼接字符串
- 逐步解析 JSON(部分解析)
- 实时渲染/更新 UI
技术方案概述
1. 使用 fetch
+ ReadableStream
接收流式数据
const response = await fetch('https://api.deepseek.com/stream-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt: '讲一个故事'
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
2. 读取 chunk 并拼接到 buffer 中
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// 尝试解析 JSON 或提取部分内容
processBuffer(buffer);
}
关键难点:如何解析未完成的 JSON?
JSON 是结构化的文本格式,如果只收到一部分内容(比如 { "text": "Hello" }
只收到 { "text": "Hel
),直接使用 JSON.parse()
会报错。
方法一:按行或分隔符切分 chunks
假设 DeepSeek 返回的是 多段 JSON 对象(每行一个完整 JSON),可以按 \n
分割:
function processBuffer(buffer) {
const lines = buffer.split('\n');
lines.forEach(line => {
if (line.trim() === '') return;
try {
const json = JSON.parse(line);
console.log(json.text); // 实时获取 text 字段
updateUI(json.text); // 实时渲染到页面
buffer = ''; // 清空已处理部分
} catch (e) {
// JSON 不完整,保留 buffer 继续拼接
}
});
}
方法二:构建 JSON 增量解析器
如果你的数据是一个完整的 JSON(例如 {"content": "Hel..."}
但被分片传输),可以尝试使用增量解析库,如:
jsonparse
(Node.js / 浏览器可用)
示例:
import sax from 'jsonparse';
const parser = new sax.Parser();
parser.onValue = function (value) {
if (this.stack.length === 1 && this.key === 'content') {
updateUI(value); // 实时渲染 content 内容
}
};
function processBuffer(buffer) {
try {
parser.write(buffer);
} catch (e) {
// 忽略不完整 JSON 错误,继续等待更多数据
}
}
实时渲染建议
你可以将每次解析出的内容追加到 DOM 中,模拟“打字效果”:
function updateUI(text) {
const container = document.getElementById('output');
container.textContent += text;
container.scrollTop = container.scrollHeight; // 滚动到底部
}
示例:整合完整流程
async function startStreaming() {
const response = await fetch('https://api.deepseek.com/stream-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ prompt: '讲一个故事' })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
const parser = new sax.Parser();
parser.onValue = function (value) {
if (this.stack.length === 1 && this.key === 'content') {
updateUI(value);
}
};
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
try {
parser.write(buffer);
buffer = ''; // 成功解析后清空 buffer
} catch (e) {
// JSON 不完整,保留 buffer 等待下一次拼接
}
}
parser.close();
}
function updateUI(text) {
const container = document.getElementById('output');
container.textContent += text;
}
注意事项
项目 | 建议 |
---|---|
后端支持 | 确保接口返回的是 text/event-stream 类型,并开启 CORS |
错误处理 | 添加网络中断、JSON 解析失败等异常捕获机制 |
性能优化 | 避免频繁操作 DOM,可使用 requestAnimationFrame 或节流 |
安全性 | 如果涉及用户输入,需做 XSS 过滤 |
完整性校验 | 最终仍需在 done 后对最终结果做一次完整 JSON 解析 |