为何重赋值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
相关产品推荐
相关产品推荐

