聊天问答文字逐步显示实现

后端

后端通过事件流逐步发送数据 (SSE),需要设置响应头的Content-Type为'text/event-stream'

SSE是一种服务器推送技术,适合单向流式传输

node示例代码

js 复制代码
const express = require('express');
const app = express();
const port = 3000;

app.get('/stream', (req, res) => {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
    const text = "嗯,我现在要了解一下合规法规方面的知识。";
    let index = 0;

    const interval = setInterval(() => {
        if (index < text.length) {
            res.write(`data: ${text.charAt(index)}\n\n`); // 逐步发送字符
            index++;
        } else {
            clearInterval(interval);
            res.end(); // 结束流
        }
    }, 100); // 每 100ms 发送一个字符

});

  


app.listen(port, () => {
    console.log(`后端服务运行在 http://localhost:${port}`);
});

前端

1.可以通过EventSource接收流式数据,通过onmessage事件监听数据,并逐字显示

注意:这种方式只能处理get请求,且无法自定义请求头或请求体

示例:

js 复制代码
<body>
    <div id="answer"></div>
    <script>
        const answerElement = document.getElementById('answer');
        // 创建 SSE 连接
        const eventSource = new EventSource('http://localhost:3000/stream');
        // 监听数据事件
        eventSource.onmessage = (event) => {
           answerElement.textContent += event.data; // 逐步显示数据
        };

        // 监听流结束事件
        eventSource.onerror = () => {
            eventSource.close(); // 关闭连接
            console.log('流已结束');
        };
    </script>
</body>

2.使用fetch + ReadableStream

注意:如果是post请求,需要使用fetch。当然XHR也可以通过 progress 事件实现对后端返回的流式数据的解析,但需要注意数据完整性和性能问题。如果需要更强大的流式数据处理能力,推荐使用 fetch + ReadableStream

  1. ReadableStream

    • 通过 response.body.getReader() 获取 ReadableStream 的读取器。

    • 使用 reader.read() 逐块读取数据。

  2. 解码数据

    • 使用 TextDecoder 将二进制数据解码为字符串。

示例:

js 复制代码
<body>
  <div id="output"></div>
  <div id="loading">Loading...</div>

  <script>
    const outputElement = document.getElementById('output');
    const loadingElement = document.getElementById('loading');

    async function fetchStreamingData() {
      const url = 'http://localhost:3000/postStream'; // 后端接口地址
      const token = 'your-auth-token'; // 鉴权 token
      const data = { key1: 'value1', key2: 'value2' }; // 请求参数

      loadingElement.style.display = 'block'; // 显示加载动画

      try {
        const response = await fetch(url, {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(data),
        });

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        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 });
          for (const char of chunk) {
            outputElement.textContent += char;
            outputElement.scrollTop = outputElement.scrollHeight; // 自动滚动
          }
        }
      } catch (error) {
        console.error('Error:', error);
        outputElement.textContent = 'Failed to load streaming data.';
      } finally {
        loadingElement.style.display = 'none'; // 隐藏加载动画
      }
    }

    fetchStreamingData();
  </script>
</body>

好啦😊已基本实现啦!当前实际开发中肯定还有一些细节需要注意,比如服务端返回的流式数据的格式,另外大多数内容都是markdown语法,如何把文本的语法格式渲染在html页面上,这些都是需要注意的哟

相关推荐
cjy00011122 分钟前
2026年9月零基础能听懂国内 FDE 讲师的课吗?
大数据·前端·人工智能·fde
南雨北斗36 分钟前
wangeditor5 在vue3项目中的正确配置
前端
Cache技术分享38 分钟前
517. Java 方法句柄 - 方法句柄 vs 反射 API
前端·后端
小刘在重生~39 分钟前
Web 前端基础|HTML+CSS+JavaScript+Bootstrap
前端·css·html
是立不是利1 小时前
第二篇:CSS 与用户体验——看不见的设计
前端·css·ux
打呵欠的猫1 小时前
我把 20 个页面的权限控制从"硬编码"改成"配置驱动",AI 帮我生成了 80% 的迁移代码
前端·ai编程
志尊宝1 小时前
Vue3 零基础每日笔记(005):ref 响应式基础——为什么 script 里要 .value
前端·javascript·vue.js·笔记
海鸥两三1 小时前
Mac电脑使用 Tabby 部署前端项目到阿里云服务器完整教程
前端·macos·阿里云
console.log('npc')1 小时前
2026 实测:Grok 4.5 与 Grok 4.6 怎么选?前端开发、教程写作、Figma 还原选型指南
前端·大模型·ai编程·figma·grok
yyt3630458411 小时前
一次鼠标交互触发两次 Vue flushJobs:把高频交互从 VDOM 里拆出来
前端·vue.js·计算机外设