首页
/ GraphQL-Request 中 memoize fetch 函数导致 "Body is unusable" 错误解析

GraphQL-Request 中 memoize fetch 函数导致 "Body is unusable" 错误解析

2025-06-05 18:05:23作者:劳婵绚Shirley

问题背景

在使用 graphql-request 库进行 GraphQL 请求时,开发者尝试通过 lodash.memoize 对 fetch 函数进行记忆化(memoization)优化,期望能够缓存重复的请求结果。然而在实际应用中却遇到了 "Body is unusable" 的错误。

技术分析

记忆化与 fetch 的冲突

记忆化是一种常见的性能优化技术,它通过缓存函数调用的结果来避免重复计算。然而,当这种技术应用于 fetch API 时,会遇到一些特殊问题:

  1. 响应体(Response Body)的一次性读取特性:fetch 返回的 Response 对象的 body 属性是一个 ReadableStream,它设计为只能被读取一次。一旦被读取,就无法再次使用。

  2. 记忆化缓存了不可复用的对象:当 memoize 缓存了 fetch 的返回结果后,后续相同的请求会直接返回缓存的 Response 对象。然而这个 Response 的 body 可能已经被前一次调用消费掉了。

错误根源

具体到 graphql-request 的使用场景中,当开发者这样配置:

export const gqlClient = new GraphQLClient(GRAPHQL_URL, {
  fetch: memoize(
    fetch,
    (...args) => JSON.stringify(args)
  ),
});

实际上是在记忆化整个 fetch 调用过程。当多个 GraphQL 请求使用相同的参数时,会返回同一个 Response 对象。而 graphql-request 内部会尝试读取这个 Response 的 body,如果该 body 已经被之前的请求读取过,就会抛出 "Body is unusable" 错误。

解决方案比较

不推荐的方案:直接记忆化 fetch

直接记忆化 fetch 函数的问题在于:

  • 缓存的是包含一次性读取流的 Response 对象
  • 无法正确处理 POST 请求中的 body 重复使用
  • 忽略了 HTTP 请求的时效性特点

推荐的替代方案

  1. 记忆化 SDK 方法
const unmemoizedGqlSdk = getSdk(gqlClient);
export const gqlSdk = Object.fromEntries(
  Object.entries(unmemoizedGqlSdk).map(([key, value]) => [
    key,
    memoize(value, (...args) => JSON.stringify(args)),
  ]),
);

这种方法更为合理,因为:

  • 在更高层级进行缓存,避免底层 fetch 的问题
  • 可以控制缓存粒度,针对每个查询方法单独处理
  • 缓存的是最终解析后的数据而非 Response 对象
  1. 使用专门的 GraphQL 缓存方案
  • Apollo Client 的缓存机制
  • URQL 的文档缓存
  • 自定义的 GraphQL 查询结果缓存层
  1. HTTP 缓存控制
  • 利用 HTTP 标准的缓存头(Cache-Control, ETag 等)
  • 服务端实现缓存策略

深入理解

为什么记忆化在 GraphQL 中需要特别处理

GraphQL 请求有一些独特的特点:

  1. 大多数是 POST 请求,即使查询相同
  2. 请求体包含查询语句和变量
  3. 响应可能随时间变化(即使查询相同)

记忆化的适用场景

在 GraphQL 客户端中,记忆化最适合用于:

  • 静态查询结果
  • 不经常变化的数据
  • 计算密集型的查询转换

记忆化的局限性

记忆化并非适用于所有场景:

  • 实时数据查询
  • 频繁变化的数据
  • 需要保证数据新鲜度的场景

最佳实践建议

  1. 评估缓存需求:明确哪些数据真正需要缓存,避免过度优化

  2. 选择合适的缓存层级

    • UI 组件级别缓存
    • 应用状态管理缓存
    • 网络请求级别缓存
  3. 考虑缓存失效策略

    • 基于时间过期
    • 基于数据版本
    • 手动清除机制
  4. 监控缓存效果:确保缓存确实带来性能提升,而非引入新问题

总结

在 graphql-request 中直接记忆化 fetch 函数会导致 "Body is unusable" 错误,这是因为 fetch 的 Response 对象设计为一次性使用。正确的做法是在更高层级实现缓存逻辑,或者使用专门的 GraphQL 缓存解决方案。理解底层机制和选择合适的缓存策略对于构建高效的 GraphQL 应用至关重要。

登录后查看全文
热门项目推荐

项目优选

收起
openHiTLS-examplesopenHiTLS-examples
本仓将为广大高校开发者提供开源实践和创新开发平台,收集和展示openHiTLS示例代码及创新应用,欢迎大家投稿,让全世界看到您的精巧密码实现设计,也让更多人通过您的优秀成果,理解、喜爱上密码技术。
C
53
465
kernelkernel
deepin linux kernel
C
22
5
openHiTLSopenHiTLS
旨在打造算法先进、性能卓越、高效敏捷、安全可靠的密码套件,通过轻量级、可剪裁的软件技术架构满足各行业不同场景的多样化要求,让密码技术应用更简单,同时探索后量子等先进算法创新实践,构建密码前沿技术底座!
C
349
381
nop-entropynop-entropy
Nop Platform 2.0是基于可逆计算理论实现的采用面向语言编程范式的新一代低代码开发平台,包含基于全新原理从零开始研发的GraphQL引擎、ORM引擎、工作流引擎、报表引擎、规则引擎、批处理引引擎等完整设计。nop-entropy是它的后端部分,采用java语言实现,可选择集成Spring框架或者Quarkus框架。中小企业可以免费商用
Java
7
0
openGauss-serveropenGauss-server
openGauss kernel ~ openGauss is an open source relational database management system
C++
132
185
RuoYi-Vue3RuoYi-Vue3
🎉 (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统
Vue
876
517
Cangjie-ExamplesCangjie-Examples
本仓将收集和展示高质量的仓颉示例代码,欢迎大家投稿,让全世界看到您的妙趣设计,也让更多人通过您的编码理解和喜爱仓颉语言。
Cangjie
336
1.1 K
ohos_react_nativeohos_react_native
React Native鸿蒙化仓库
C++
179
264
cherry-studiocherry-studio
🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端
TypeScript
610
59
note-gennote-gen
一款跨平台的 Markdown AI 笔记软件,致力于使用 AI 建立记录和写作的桥梁。
TSX
83
4