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

为何重赋值res.json仍报“Response的text执行失败:body流已读取”错误?

问题:graphql-request结合APQ时响应体重复读取报错

背景

我尝试用一段代码结合APQ(自动持久化查询)和graphql-request实现功能,但使用时浏览器出现报错。

代码使用方式

export const graphQLClient = new GraphQLClient("http://localhost:3000", {
    fetch: createPersistedQueryFetch(fetch),
});

报错信息

TypeError: Failed to execute 'text' on 'Response': body stream already read
    at getResult (index.ts:639:48)
    at makeRequest (index.ts:394:24)
    at async Object.queryFn (utils.ts:84:25)

问题代码片段

我认为问题出在createPersistedQueryFetch函数的这段代码:

export const createPersistedQueryFetch =
  (fetchImpl: Fetch): Fetch =>
  async (info, init) => {
    // ...

    // 发送不带查询语句的请求
    const res = await fetchImpl(
      requestWithoutQuery.info,
      requestWithoutQuery.init
    );
    const body = await res.json();

    // 如果服务器未找到查询,发送带查询哈希的请求
    if (isPersistedQueryNotFoundError(body)) {
      return fetchImpl(requestWithQueryHash.info, requestWithQueryHash.init);
    } else {
      res.json = () => Promise.resolve(body);
      return res;
    }
  };

核心疑问

graphql-request会尝试重新读取createPersistedQueryFetch里已经读过的响应体,但为什么重写res.json = () => Promise.resolve(body)无法解决这个问题?


解答

问题的核心是:你只重写了res.json()方法,但graphql-request内部实际调用了res.text()方法——从报错信息里的Failed to execute 'text' on 'Response'就能直接印证这一点。

浏览器原生Response对象的body是一次性可读流,一旦通过json()/text()等方法读取后,流就会被彻底消耗,无法再次读取。你虽然覆盖了json()让它返回缓存的body,但text()还是原生实现,调用它时依然会尝试读取已经被消耗的流,因此触发报错。

两种解决办法

方法1:创建新的Response对象

读取响应后,用缓存的数据创建一个全新的Response对象,复用原响应的状态码、头等元信息,这样所有读取方法(json()、text()等)都能正常工作:

export const createPersistedQueryFetch =
  (fetchImpl: Fetch): Fetch =>
  async (info, init) => {
    // ...

    const res = await fetchImpl(
      requestWithoutQuery.info,
      requestWithoutQuery.init
    );
    const body = await res.json();

    if (isPersistedQueryNotFoundError(body)) {
      return fetchImpl(requestWithQueryHash.info, requestWithQueryHash.init);
    } else {
      // 重新封装响应,确保body流可重复读取
      return new Response(JSON.stringify(body), {
        status: res.status,
        statusText: res.statusText,
        headers: res.headers,
      });
    }
  };

方法2:同时覆盖所有可能被调用的读取方法

如果不想创建新Response,可以同时覆盖text()(以及其他可能被调用的方法,如blob()),让它们都返回缓存的内容:

export const createPersistedQueryFetch =
  (fetchImpl: Fetch): Fetch =>
  async (info, init) => {
    // ...

    const res = await fetchImpl(
      requestWithoutQuery.info,
      requestWithoutQuery.init
    );
    const body = await res.json();
    const bodyText = JSON.stringify(body);

    if (isPersistedQueryNotFoundError(body)) {
      return fetchImpl(requestWithQueryHash.info, requestWithQueryHash.init);
    } else {
      res.json = () => Promise.resolve(body);
      res.text = () => Promise.resolve(bodyText);
      // 若有需要,可继续覆盖blob()等其他读取方法
      return res;
    }
  };

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 20:40:06