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

后端

后端通过事件流逐步发送数据 (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页面上,这些都是需要注意的哟

相关推荐
随风一样自由1 小时前
【前端+项目分析】`img` vs `Image`:从两个真实项目看前端图片组件的正确选择
前端·image·img·项目对比分析
倾颜1 小时前
断线之后,不要重跑 AI:在 POST + NDJSON 中实现可恢复 Agent 流
前端·后端·agent
程序员黑豆1 小时前
鸿蒙应用开发之父子组件传参:@Prop 装饰器使用教程
前端·后端·harmonyos
信也科技布道师1 小时前
从绝对定位到可维护页面:一次 MasterGo 还原链路的实战复盘
前端
程序员黑豆2 小时前
鸿蒙应用开发之V2状态管理:@Local、@ObservedV2、@Trace 使用教程
前端·后端·harmonyos
锻炼²2 小时前
Edge 地址栏搜索被其他浏览器劫持
前端·edge
Slice_cy2 小时前
Mint 自研框架设计与实现:从重复开发走向配置驱动(三)
前端·后端·架构
JoyT2 小时前
Nuxt 3的核心设计与静态官网应用
前端·javascript·vue.js
程序员黑豆2 小时前
鸿蒙应用开发之@State 装饰器详解:从基本类型到 @Observed/@ObjectLink/@Track 嵌套监听
前端·harmonyos