You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue.js中如何实现流式API的逐块文本实时展示?

Vue.js中如何实现流式API的逐块文本实时展示?

嘿,这个问题我之前踩过坑!其实你碰到的核心问题是服务器端的输出缓冲加上客户端的文本解码/片段处理没做到位,下面我一步步帮你搞定:

第一步:先搞定服务器端(最关键!)

很多后端服务器(比如Node.js、Nginx、Flask)默认会把输出内容攒到一定大小才发送给客户端,哪怕你逻辑上是每秒发一个单词。所以必须让服务器每次发送完一小段数据就立刻“推送”出去,不能让它缓冲。

举个Node.js原生服务器的示例(如果你用Node.js后端的话):

const http = require('http');
const server = http.createServer((req, res) => {
  // 开启分块传输编码,告诉客户端这是流式响应
  res.writeHead(200, {
    'Content-Type': 'text/plain; charset=utf-8',
    'Transfer-Encoding': 'chunked'
  });

  // 模拟每秒发送一个单词
  const words = ['Hello', 'Vue.js', '流式', 'API', '实战'];
  let wordIndex = 0;
  const timer = setInterval(() => {
    if (wordIndex >= words.length) {
      clearInterval(timer);
      res.end(); // 结束流式响应
      return;
    }
    // 发送单个单词+空格(方便客户端分割)
    res.write(`${words[wordIndex]} `);
    // 强制刷新缓冲区,把数据立刻推送给客户端
    if (res.flush) res.flush();
    wordIndex++;
  }, 1000);
});
server.listen(3000);

要是你用的是Express框架,Express 4.16.0+也支持res.flush(),用法和上面一致。其他后端框架的话,比如Python Flask,需要关闭响应缓冲(比如调用response.flush()),具体可以查对应框架的文档。

第二步:Vue客户端的优化实现

你的原代码有两个小问题:一是没把Uint8Array类型的chunk转成正确的字符串,二是没考虑到可能的不完整文本片段(比如某次chunk只发了半个单词)。下面是Vue组件里的完整实现:

<template>
  <div class="stream-container">
    <p>{{ displayedText }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      displayedText: ''
    };
  },
  async mounted() {
    // 组件挂载后启动流式请求
    await this.startTextStreaming();
  },
  methods: {
    async startTextStreaming() {
      try {
        const response = await fetch('https://my-streaming-api', {
          headers: {
            'Accept': 'text/plain' // 明确请求纯文本类型
          }
        });

        if (!response.ok) {
          throw new Error(`请求失败:HTTP状态码 ${response.status}`);
        }

        const reader = response.body.getReader();
        const decoder = new TextDecoder('utf-8');
        let textBuffer = ''; // 存储不完整的文本片段,避免截断单词或多字节字符

        while (true) {
          const { done, value } = await reader.read();
          if (done) {
            // 处理最后剩余的文本片段
            if (textBuffer) {
              this.displayedText += textBuffer;
            }
            break;
          }

          // 解码二进制数据,stream: true表示后续还有数据,避免截断多字节字符
          const chunk = decoder.decode(value, { stream: true });
          textBuffer += chunk;

          // 按空格分割成单词(根据你的实际分隔符调整)
          const wordList = textBuffer.split(' ');
          // 遍历除最后一个元素外的所有完整单词
          for (let i = 0; i < wordList.length - 1; i++) {
            if (wordList[i]) { // 跳过空字符串
              this.displayedText += `${wordList[i]} `;
            }
          }
          // 把最后一个不完整的片段放回缓冲区,等待下一个chunk
          textBuffer = wordList[wordList.length - 1];
        }
      } catch (error) {
        console.error('流式请求出错:', error);
        this.displayedText = '请求出错,请稍后重试';
      }
    }
  }
};
</script>

<style scoped>
.stream-container {
  padding: 20px;
  font-size: 18px;
}
</style>

几个关键细节要注意

  • TextDecoder的stream: true选项:这个非常重要!如果你的文本包含多字节字符(比如中文、Emoji),某次chunk可能刚好截断一个字符,用这个选项可以让解码器暂存不完整的字符,等下一个chunk来了再一起解码,避免出现乱码。
  • 文本缓冲区textBuffer:服务器发送的chunk不一定刚好是完整的单词,比如这次发了Hel,下次发lo,缓冲区会把这两个拼起来变成Hello再分割,保证你展示的都是完整的单词。
  • 响应式数据更新:displayedText是Vue的响应式数据,每次追加单词后,页面会自动实时更新,不需要手动操作DOM。

最后再强调一遍:服务器端的配置是核心,如果服务器没开启分块传输或者没及时flush缓冲区,客户端再怎么优化都拿不到实时的chunk!

备注:内容来源于stack exchange,提问作者kevinpeterson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 14:09:36