以下是一個完整的 JavaScript 數據流式獲取實現方案,模擬類似 DeepSeek 的對話式逐段返回效果。包含前端實現、后端模擬和詳細注釋:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>流式對話演示</title><style>#output {border: 1px solid #ccc;padding: 20px;margin: 20px;min-height: 200px;font-family: monospace;white-space: pre-wrap;}.loading {display: none;color: #666;}</style>
</head>
<body><button onclick="startStream()">開始對話</button><div id="output"></div><div id="loading" class="loading">思考中...</div><script>// 流式對話核心邏輯class StreamDialog {constructor() {this.output = document.getElementById('output');this.loading = document.getElementById('loading');this.controller = null; // 用于中止請求}// 開始流式對話async start() {try {this.showLoading();this.output.textContent = '';// 創建中止控制器this.controller = new AbortController();// 發起 fetch 請求const response = await fetch('/stream', {signal: this.controller.signal,headers: {'Content-Type': 'application/json'}});if (!response.ok) throw new Error(response.statusText);if (!response.body) throw new Error("ReadableStream not supported");// 創建讀取器const reader = response.body.getReader();const decoder = new TextDecoder();// 持續讀取流數據while (true) {const { done, value } = await reader.read();if (done) break;// 解析數據塊const chunk = decoder.decode(value);const lines = chunk.split('\n').filter(line => line.trim());for (const line of lines) {try {const event = JSON.parse(line.replace('data: ', ''));this.appendContent(event.content);} catch (e) {console.error('解析錯誤:', e);}}}} catch (error) {if (error.name !== 'AbortError') {this.appendContent('\n[對話異常終止]');}} finally {this.hideLoading();this.controller = null;}}// 中止對話abort() {if (this.controller) {this.controller.abort();}}// 添加內容到輸出區appendContent(text) {// 模擬打字機效果let currentIndex = 0;const animate = () => {if (currentIndex < text.length) {this.output.textContent += text[currentIndex];currentIndex++;requestAnimationFrame(animate);this.output.scrollTop = this.output.scrollHeight; // 自動滾動}};animate();}showLoading() {this.loading.style.display = 'block';}hideLoading() {this.loading.style.display = 'none';}}// 初始化對話實例const dialog = new StreamDialog();// 啟動對話function startStream() {dialog.start();}// 中止對話function stopStream() {dialog.abort();}</script>
</body>
</html>
后端 Node.js 實現(Express):
const express = require('express');
const app = express();
const port = 3000;// 允許跨域
app.use((req, res, next) => {res.header('Access-Control-Allow-Origin', '*');next();
});// 流式端點
app.get('/stream', (req, res) => {// 設置流式響應頭res.writeHead(200, {'Content-Type': 'text/event-stream','Cache-Control': 'no-cache','Connection': 'keep-alive'});// 模擬的對話數據const responseText = ["你好!我是 DeepSeek 的智能助手。","我正在分析您的問題...","根據現有數據,建議如下:\n","1. 首先檢查網絡連接\n","2. 驗證 API 密鑰有效性\n","3. 查看服務狀態面板\n","\n需要更詳細的幫助嗎?"];// 發送流式數據let index = 0;const sendChunk = () => {if (index < responseText.length) {const chunk = {content: responseText[index],finished: index === responseText.length - 1};// SSE 格式要求res.write(`data: ${JSON.stringify(chunk)}\n\n`);index++;setTimeout(sendChunk, 500); // 控制發送速度} else {res.end();}};sendChunk();// 處理客戶端斷開連接req.on('close', () => {console.log('客戶端斷開連接');res.end();});
});app.listen(port, () => {console.log(`服務運行在 http://localhost:${port}`);
});
核心實現原理:
- 前端實現:
- 使用 Fetch API + ReadableStream 處理流式數據
- 支持請求中止(AbortController)
- 實現打字機動畫效果
- 自動滾動保持內容可見
- 加載狀態指示
- 后端實現:
- 使用 Server-Sent Events (SSE) 協議
- 分塊發送 JSON 格式數據
- 模擬真實響應延遲(500ms/塊)
- 支持客戶端斷開檢測
- 數據格式:
{"content": "當前內容片段","finished": false
}
優化擴展建議:
- 性能優化:
// 添加節流控制
const throttle = (func, limit) => {let lastFunc;let lastRan;return function(...args) {if (!lastRan) {func.apply(this, args);lastRan = Date.now();} else {clearTimeout(lastFunc);lastFunc = setTimeout(() => {if ((Date.now() - lastRan) >= limit) {func.apply(this, args);lastRan = Date.now();}}, limit - (Date.now() - lastRan));}};
};// 在 appendContent 中使用
this.appendContent = throttle(this.appendContent, 50);
- 錯誤處理增強:
// 前端添加錯誤處理
async start() {try {// ...原有邏輯...} catch (error) {if (error.name === 'AbortError') {this.appendContent('\n[對話已中止]');} else {this.appendContent(`\n[錯誤: ${error.message}]`);console.error('Stream Error:', error);}}
}// 后端添加錯誤模擬
app.get('/stream', (req, res) => {// 10% 概率模擬錯誤if (Math.random() < 0.1) {res.writeHead(500);res.end();return;}// ...原有邏輯...
});
- 功能擴展:
// 添加 Markdown 支持
appendContent(text) {// 簡單 Markdown 解析const parsed = text.replace(/#{3}/g, '<h3>').replace(/#{2}/g, '<h2>').replace(/#{1}/g, '<h1>').replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');// 使用 DocumentFragment 優化渲染const fragment = document.createDocumentFragment();const span = document.createElement('span');span.innerHTML = parsed;fragment.appendChild(span);this.output.appendChild(fragment);
}
這個實現方案完整展示了:
- 前后端流式通信的全流程
- 實時內容渲染優化技巧
- 完整的錯誤處理機制
- 可擴展的架構設計
- 用戶體驗優化細節
實際部署時,建議:
- 使用 WebSocket 替代 SSE 實現雙向通信
- 添加 JWT 鑒權
- 實現速率限制(Rate Limiting)
- 部署到支持 HTTP/2 的服務器
- 添加前端緩存策略