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

React中Fetch分块接收Heroku服务数据提前终止问题求助

问题分析与解决方案

可能的原因

1. TextDecoder 重复创建引发解码异常

每次循环都新建TextDecoder实例,无法处理跨分块的多字节字符(如中文、emoji)。当分块刚好截断多字节字符时,新解码器无法识别残留字节片段,会导致解码后的字符串乱码或截断,破坏JSON结构,进而无法触发完整数组的判断逻辑,数据处理提前终止。

2. 分块处理逻辑过于严苛

当前代码仅当partialResult是完整JSON数组(以[开头且]结尾)时才处理数据,但分块传输的响应可能将一个大数组拆分为多个不完整片段发送(比如第一个分块是[{},{},,第二个是{},...]),中间分块无法满足判断条件,会持续累积。若某次分块后累积字符串未构成完整数组,且后续无更多分块,就会出现数据提前停止的情况。

3. 未处理循环结束时的剩余数据

当reader.read()返回done: true时,partialResult中可能残留未处理的完整数组片段,但当前代码直接跳出循环,这部分数据会被丢弃。

4. 频繁React状态更新阻塞读取流程

循环中每次处理完数据就调用setMatches,而React状态更新是异步且会触发组件重渲染的。频繁状态更新会占用主线程资源,导致reader.read()执行延迟,甚至出现读取超时,后续分块无法被及时处理。

修复后的代码

import { parse } from "partial-json";

const [matches, setMatches] = useState(null);

const getFeed = async () => {
  try {
    const response = await fetch(endpoint);

    if (!response.ok) {
      throw new Error("Failed to fetch data");
    }

    const reader = response.body.getReader();
    const decoder = new TextDecoder(); // 仅创建一次解码器
    let partialResult = "";
    let localResults = []; // 本地缓存数据,减少状态更新次数

    while (true) {
      const { done, value } = await reader.read();

      if (done) {
        // 处理循环结束时剩余的数据
        if (partialResult.trim()) {
          try {
            const finalChunk = parse(partialResult);
            if (Array.isArray(finalChunk)) {
              localResults = [...localResults, ...finalChunk];
            }
          } catch (e) {
            console.error("解析剩余数据失败:", e);
          }
        }
        break;
      }

      // 使用同一个解码器处理分块,避免多字节字符解析问题
      partialResult += decoder.decode(value, { stream: true });

      try {
        // 尝试逐步解析部分JSON,无需等待完整数组
        const parsed = parse(partialResult);
        if (Array.isArray(parsed)) {
          // 将解析出的数组合并到本地缓存
          localResults = [...localResults, ...parsed];
          // 清空已解析的部分,保留未完成的片段(如数组末尾的不完整对象)
          partialResult = "";
        }
      } catch (e) {
        // 解析失败说明数据未完整,继续累积即可
        continue;
      }
    }

    // 统一处理去重与排序,再更新状态
    const uniqueResults = Array.from(
      new Set(localResults.map((item) => beautifyURL(item.title)))
    )
      .map((productId) =>
        localResults.find((item) => beautifyURL(item.title) === productId)
      )
      .sort((a, b) => Number(a.price) - Number(b.price));

    setMatches(uniqueResults);
  } catch (error) {
    console.error(error);
  } finally {
    setLoading(false);
  }
};

额外优化建议

  • 调整后端分块格式:若可修改后端,建议使用NDJSON(每行一个JSON对象)格式传输,前端按行解析即可,逻辑更简单,避免partial JSON解析的复杂性。
  • 添加超时处理:给reader.read()添加超时逻辑,避免因网络问题导致无限等待。
  • 监控读取过程:在循环中添加console.log打印done和value.length,确认是否收到所有分块,排查是前端未读取还是后端未发送。

内容的提问来源于stack exchange,提问作者jonask

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 13:28:35