Fetch调用response.json()出现body-timeout错误的原因及解决咨询
咱们先拆解触发这个body-timeout错误的两个核心问题:
原生Fetch API不支持
timeout配置参数
你在fetch的配置里加了timeout: REQUEST_TIMEOUT_MS,但这个参数完全无效——原生Fetch根本没有这个选项。这意味着你的请求不会自动终止超时请求,当目标API响应极慢或挂起时,请求会一直等待到浏览器/运行时的默认超时(通常远长于你设置的10000ms),最终在读取响应体时触发body-timeout错误。Response的响应体流只能被读取一次
你用cloneDeep(response)传递给foo方法,但cloneDeep(比如Lodash的深拷贝)无法正确克隆Fetch的Response对象——它的body是一个只读的ReadableStream,只能被消费一次。当你在主代码后续执行await response.json()时,已经把响应体的流耗尽了,此时foo里再调用await response.json(),要么直接报错,要么会因为等待已关闭的数据流而超时。
针对这两个问题,咱们一步步修复:
1. 给Fetch添加真正的超时控制
用AbortController手动实现超时终止,这是原生Fetch推荐的超时方案:
const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); try { response = await fetch(requestUrl, { method: 'POST', headers, signal: controller.signal, // 关联终止信号 body: JSON.stringify({ id, accNumber: acctId, invNumber: invId, fromDate, toDate }) }); } catch (error) { if (error.name === 'AbortError') { throw new Error('Request timed out while invoking ATOM Invoice API'); } else { throw new Error('Unexpected error occurred while invoking ATOM Invoice API', { cause: error }); } } finally { clearTimeout(timeoutId); // 清理定时器,避免内存泄漏 }
这样请求超过设定时间后会主动终止,抛出明确的AbortError,不会一直挂起等待。
2. 正确克隆Response以多次读取响应体
不要用cloneDeep,改用Fetch原生的response.clone()方法——它会创建一个带有独立响应体流的全新Response对象,主代码和foo方法可以各自读取:
// 主代码中 const clonedResponse = response.clone(); foo(clonedResponse); // 传递克隆后的Response const result = await response.json(); // 原Response的流仍可正常读取
foo方法的代码可以保留,但建议优化错误日志的可读性:
async foo(response){ let result; let errorDescription; try { result = await response.json(); } catch (error) { logger.error(`Failed to parse response body: ${JSON.stringify(error)}`); // 这里可以添加 fallback 逻辑,比如返回默认值或标记错误状态 } // --- some more code --- }
额外优化建议
- 检查HTTP状态码:当前代码没有处理API返回的4xx/5xx错误,Fetch只有在网络层面失败时才会抛出异常,建议在获取response后添加:
if (!response.ok) { throw new Error(`ATOM Invoice API returned error: ${response.status} ${response.statusText}`); } - 传递原始错误:在抛出自定义错误时,使用
{ cause: error }携带原始错误信息,方便后续排查问题。
内容的提问来源于stack exchange,提问作者Eranga Heshan

