前端处理流式数据

前言

本篇文章为笔者开发过程中总结的关于前端处理后端流式接口返回的流式数据

示例


export default {
    data() {
        return {
            allMessages: [], // 存储所有消息的数组
            _Message: {
                content: '', // 存储当前消息的内容
                // 其他消息属性可以在此处添加
            }
        };
    },
    methods: {
        async send() {
            // 构建API请求的URL
            const url = '你的后端api';

            // 构建API请求的初始化参数
            const init = {
                method: 'post',
                headers: {
                    'Content-Type': 'application/json',
                    // 你可以在此处添加其他后端所需的头信息
                    // 'Authorization': 'Bearer your_token',
                },
                body: JSON.stringify({
                    // 填写后端所需的参数
                    param1: 'value1',
                    param2: 'value2',
                }),
            };

            try {
                // 发起异步网络请求
                const response = await fetch(url, init);

                // 检查网络响应是否成功
                if (!response.ok) {
                    throw new Error(`HTTP error! Status: ${response.status}`);
                }

                // 获取响应体的数据读取器
                const reader = response.body.getReader();
                // 创建用于解码文本的解码器
                const decoder = new TextDecoder('utf-8');

                // 创建一个可读流
                const stream = new ReadableStream({
                    async start(controller) {
                        while (true) {
                            // 从数据读取器中读取数据
                            const { done, value } = await reader.read();

                            // 如果读取完成,关闭控制器并退出循环
                            if (done) {
                                controller.close();
                                break;
                            }

                            // 解码值并将其附加到指定ID的消息内容中
                            const match = decoder.decode(value, { stream: true });
                            if (match) {
                                // 更新当前消息的内容
                                this._Message.content += match;

                                // 更新 Vuex 中的 streamingData 状态
                                store.commit('updateStreamingData', match);
                            }
                        }
                    },
                });

                // 消费流数据(实际上可以根据需要处理)
                new Response(stream).text().then(text => {
                    console.log('Streamed data received:', text);
                });

                // 输出一条日志,表示流数据已接收完毕
                console.log('Full data received');

            } catch (error) {
                // 捕获并打印任何网络请求错误
                console.error('Fetch error:', error);
            }

            // 添加当前消息到 allMessages 数组
            this.allMessages.push(this._Message);

            // 打印所有消息
            console.log(this.allMessages);
        }
    }
};

<script>
import { ref, reactive } from 'vue';
import { useStore } from 'vuex';

export default {
  setup() {
    // 使用 Vuex store
    const store = useStore();

    // 定义消息和所有消息的状态
    const _Message = reactive({
      content: '', // 当前消息的内容
    });
    const allMessages = ref([]); // 存储所有消息的数组

    const send = async () => {
      // 构建API请求的URL
      const url = '你的后端api';

      // 构建API请求的初始化参数
      const init = {
        method: 'post',
        headers: {
          'Content-Type': 'application/json',
          // 你可以在此处添加其他后端所需的头信息
          // 'Authorization': 'Bearer your_token',
        },
        body: JSON.stringify({
          // 填写后端所需的参数
          param1: 'value1',
          param2: 'value2',
        }),
      };

      try {
        // 发起异步网络请求
        const response = await fetch(url, init);

        // 检查网络响应是否成功
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }

        // 获取响应体的数据读取器
        const reader = response.body.getReader();
        // 创建用于解码文本的解码器
        const decoder = new TextDecoder('utf-8');

        // 创建一个可读流
        const stream = new ReadableStream({
          async start(controller) {
            while (true) {
              // 从数据读取器中读取数据
              const { done, value } = await reader.read();

              // 如果读取完成,关闭控制器并退出循环
              if (done) {
                controller.close();
                break;
              }

              // 解码值并将其附加到指定ID的消息内容中
              const match = decoder.decode(value, { stream: true });
              if (match) {
                // 更新当前消息的内容
                _Message.content += match;

                // 更新 Vuex 中的 streamingData 状态
                store.commit('updateStreamingData', match);
              }
            }
          },
        });

        // 消费流数据(实际上可以根据需要处理)
        new Response(stream).text().then((text) => {
          console.log('Streamed data received:', text);
        });

        // 输出一条日志,表示流数据已接收完毕
        console.log('Full data received');
      } catch (error) {
        // 捕获并打印任何网络请求错误
        console.error('Fetch error:', error);
      }

      // 添加当前消息到 allMessages 数组
      allMessages.value.push({ ..._Message });

      // 清空当前消息内容
      _Message.content = '';

      // 打印所有消息
      console.log(allMessages.value);
    };

    return {
      _Message,
      allMessages,
      send,
    };
  },
};
</script>

流程说明

1. 初始化请求参数:

  • 定义API的URL和请求的初始化参数,包括请求方法、头部信息和请求体。

2. 发送网络请求:

  • 使用fetch函数发送POST请求到后端API。
  • 等待并获取响应。

3. 处理响应:

  • 检查响应是否成功(response.ok)。
  • 如果响应成功,则获取响应体的数据读取器(reader)。
  • 创建一个可读流(stream)来处理响应体的数据。

4. 流数据处理循环:

  • 在ReadableStream的start函数中,通过reader.read()循环读取流中的数据块。
  • 检查每次读取的数据块是否完成(done为true)。
  • 如果数据块未完成,使用TextDecoder解码数据块并将解码后的文本内容附加到消息对象的内容中。
  • 如果数据块完成,关闭流的控制器并退出循环。

5. 状态更新与日志输出:

  • 在流处理完成后,输出一条日志表示流数据已接收完毕。
  • 可选的状态更新操作(如注释中提到的Vuex状态更新)。

6. 错误处理:

  • 使用try-catch结构捕获并处理可能出现的错误,如网络请求错误或流处理错误。
  • 如果出现错误,输出错误信息。

7. 消息日志输出:

  • 输出所有消息的日志,用于调试和跟踪消息处理过程。
  • 19
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋书一叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值