FetchAPI 请求流式数据 基本用法

请求 & 请求取消

src\api\fetch.ts

javascript 复制代码
// 创建一个 AbortController 实例,用于取消请求
const controller = new AbortController();
const signal = controller.signal;

// 封装整个请求
//     url:要请求的接口
//     content:要发送的问题参数
//     onMessageReceived:流式数据获取成功后的回调函数
//     end:流式数据获取结束后的回调函数
export const fetchStreamData = async (
  url: any,
  content: any,
  onMessageReceived: any,
  end: any
) => {
  try {
    // 发起fetch请求
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(content),
      signal,
    });

    const reader = response.body.getReader(); // 流式数据读取器
    let decoder = new TextDecoder(); // 解码器

    // 数据
    let buffer = '';

    while (1) {
      // 异步读取
      const { done, value } = await reader.read();
      if (done) {
        //全部获取流式数据结束后的操作
        end();
        break;
      }

      // 将读取到的数据添加到缓冲区(可能是因为客户端处理数据的速度比服务器发送数据的速度快,
      //导致一次性接收到多条数据。这时候就要用buffer缓冲区来处理一下)
      //后端给我的数据格式:{"chunk_message": "我是数据"}

      // 流式处理‌:当数据以分块形式传输(如网络流、文件分块读取等),每个数据块可能包含字符的一部分字节。
      // stream设置为 true 时,解码器会保留未完成字符的字节,与下一块数据合并解码,避免乱码。 ‌
      // ‌stream 默认值为 false,适用于非流式数据(如完整文件或一次性接收的数据)。
      buffer += decoder.decode(value, { stream: true });

      // 尝试解析每条消息
      let startIndex = buffer.indexOf('{');
      let endIndex = buffer.indexOf('}', startIndex);

      while (startIndex !== -1 && endIndex !== -1) {
        const resultData = buffer.substring(startIndex, endIndex + 1);
        buffer = buffer.substring(endIndex + 1); // 移除已处理的数据

        // 寻找下一条消息的起始位置
        startIndex = buffer.indexOf('{');
        endIndex = buffer.indexOf('}', startIndex);

        // 定义正则表达式来匹配 chunk_message 的值,我们只要chunk_message对应的值
        //因为json数据和text/event-stream数据不同,json数据可以通过.访问chunk_message 的值,
        //但是text/event-stream数据是个纯文本,不能通过.访问chunk_message,我们用正则表达式来获取需要的数据
        let pattern = /"chunk_message"\s*:\s*"([^"]*)"/;

        // 使用正则表达式进行匹配
        let match = resultData.match(pattern);

        if (match) {
          // 提取匹配到的值
          let chunkMsg = match[1];
          // 将消息传递给回调函数处理
          onMessageReceived(chunkMsg);
        } else {
          // console.log("No chunk_message found");
        }
      }
    }
  } catch (error) {
    if (error.name === 'AbortError') {
      console.error('请求取消');
    } else {
      console.error('Fetch error:', error);
    }
  }
};
// 取消请求
export const abortedFetch = () => {
  controller.abort();
};
相关推荐
lerhxx6 小时前
我用 R3F 手搓了一个能走进去的 3D 迷宫简历(上):从选型架构到迷宫生成算法
前端·javascript·three.js
鹏多多11 小时前
PC 网站接入微信登录,这 10 个坑我替你踩完了!
前端·javascript·vue.js
用户2986985301412 小时前
React 中 HTML 内容转 Word 文档的实现方案
javascript·react.js·html
mONESY12 小时前
我用 Next.js 16 + Supabase 从零做了个「背单词」H5 应用
javascript
boooooooom12 小时前
手把手做一个图 RAG 烹饪问答系统:Neo4j + Milvus + LLM 的工程实践
前端·javascript·后端
染指111015 小时前
103.RAG-LLamaIndex后端rag问答-聊天接口
前端·javascript·vue.js·人工智能
单线程_011 天前
从案例分析 Vue3 Tokenizer+Parser 源码三
前端·javascript·vue.js
小磊哥er1 天前
深入解构Claude Code - 第 8 篇 · 数据放哪、钱怎么算
javascript·ai编程
kyriewen1 天前
我装了30多个Skill,给AI安排了8个岗位
前端·javascript·ai编程
默_笙1 天前
🔥 让 AI 帮我写完整个 Next.js 博客:框架就是 AI 的"上下文 buff"
前端·javascript