tangyuxian
文章85
标签39
分类5

文章分类

文章归档

js-流式传输数据

js-流式传输数据

总结前端流式传输数据的相关问题

一、核心结论

AI 对话文字逐步显示的核心是 「流式数据传输 + 前端逐段渲染」:后端以 “数据流(Stream)” 形式分批次返回文本片段,前端监听数据流的 “分片接收” 事件,逐段将文本插入 DOM,并配合定时器控制显示速度,模拟 “打字机” 效果;无后端时也可纯前端拆分文本、定时器逐字符渲染(用于演示)。

二、核心技术原理

环节 实现逻辑
后端 不再一次性返回完整文本,通过「HTTP 分块传输(Chunked)」/「SSE」/「WebSocket」分批次推送文本片段;
前端 监听数据流接收事件(如 Fetch 的reader.read()、WebSocket 的onmessage),逐段解码并追加到 DOM;
体验优化 定时器控制每段文本的显示间隔,模拟打字速度,支持暂停 / 继续、异常兜底等;

三、分场景实现

场景 1:纯前端模拟(无后端,快速演示效果)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="chat-content"></div>
<button onclick="simulateStreamText('您好!我是AI助手,很高兴为您解答问题。', 'chat-content', 80)">
  触发流式显示
</button>

<script>
// 核心函数:模拟流式文本显示
function simulateStreamText(fullText, containerId, speed = 50) {
  const container = document.getElementById(containerId);
  container.textContent = ''; // 清空原有内容
  let index = 0; // 记录当前渲染位置

  // 递归逐字符渲染
  function typeNextChar() {
    if (index >= fullText.length) return; // 渲染完成
    container.textContent += fullText[index]; // 追加单个字符
    index++;
    setTimeout(typeNextChar, speed); // 控制打字速度
  }

  typeNextChar();
}
</script>

场景 2:Fetch + HTTP 流式响应(对接 AI API 主流方案,如 OpenAI)

① 前端代码(核心)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
async function streamChatResponse(prompt) {
  const chatContainer = document.getElementById('chat-content');
  chatContainer.textContent = '';

  try {
    const response = await fetch('/api/chat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ prompt })
    });

    // 检查是否为流式响应
    if (!response.body) throw new Error('非流式响应');
    const reader = response.body.getReader();
    const decoder = new TextDecoder('utf-8'); // 解码二进制流

    // 循环读取流片段
    while (true) {
      const { done, value } = await reader.read();
      if (done) break; // 流结束

      // 解码二进制数据为文本片段
      const chunk = decoder.decode(value, { stream: true });
      // 模拟打字速度(可根据需求调整间隔)
      await new Promise(resolve => setTimeout(resolve, 50));
      chatContainer.textContent += chunk;
    }
  } catch (e) {
    // 异常兜底:直接显示完整文本
    chatContainer.textContent = '抱歉,响应失败:' + e.message;
  }
}
② 后端示例(Node.js/Express,模拟 AI 流式返回)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const express = require('express');
const app = express();
app.use(express.json());

// 流式聊天接口
app.post('/api/chat', (req, res) => {
  // 开启分块传输(核心)
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
  res.setHeader('Transfer-Encoding', 'chunked');

  const { prompt } = req.body;
  // 模拟AI生成文本的过程,分批次返回
  const fullText = '您好!我是AI助手,您的问题是:' + prompt + ',我会逐步解答。';
  const chunkSize = 2; // 每次返回2个字符
  let index = 0;

  // 定时推送文本片段
  const interval = setInterval(() => {
    if (index >= fullText.length) {
      clearInterval(interval);
      res.end(); // 结束流
      return;
    }
    const chunk = fullText.slice(index, index + chunkSize);
    res.write(chunk); // 写入分块数据
    index += chunkSize;
  }, 100);
});

app.listen(3000, () => console.log('后端启动:http://localhost:3000'));

场景 3:WebSocket(实时双向交互,适合长连接聊天)

WebSocket 是全双工通信,后端可主动推送文本片段,适合需要 “实时交互” 的场景(如在线客服):

① 前端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function initWebSocketChat() {
  const chatContainer = document.getElementById('chat-content');
  const ws = new WebSocket('ws://localhost:3000/ws/chat');

  // 连接成功后发送提问
  ws.onopen = () => ws.send(JSON.stringify({ prompt: '你好,介绍下自己' }));

  // 接收后端推送的文本片段
  ws.onmessage = (e) => {
    const { content } = JSON.parse(e.data);
    // 打字机效果
    setTimeout(() => {
      chatContainer.textContent += content;
    }, 50);
  };

  // 连接关闭处理
  ws.onclose = () => console.log('WebSocket连接关闭');
}
② 后端示例(Node.js/ws 库)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000 });

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const { prompt } = JSON.parse(data);
    const fullText = `WebSocket流式响应:你问的是「${prompt}」,我正在逐步回复...`;
    let index = 0;

    // 逐字符推送
    const interval = setInterval(() => {
      if (index >= fullText.length) {
        clearInterval(interval);
        return;
      }
      ws.send(JSON.stringify({ content: fullText[index] }));
      index++;
    }, 50);
  });
});

四、对接OPENAI效果

OpenAI 返回的流式数据是data: {"content":"xx"}\n\n格式,需拆分解析:

1
2
3
4
5
6
7
8
9
// 处理OpenAI流式响应示例
const lines = chunk.split('\n').filter(line => line.trim() !== '');
for (const line of lines) {
  const data = line.replace(/^data: /, '');
  if (data === '[DONE]') continue; // 忽略结束标记
  const json = JSON.parse(data);
  const content = json.choices[0].delta.content;
  if (content) chatContainer.textContent += content;
}
本文作者:tangyuxian
本文链接:https://www.tangyuxian.com/2025/12/06/%E5%89%8D%E7%AB%AF/JavaScript/js-%E6%B5%81%E5%BC%8F%E4%BC%A0%E8%BE%93%E6%95%B0%E6%8D%AE/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可