
前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载Relay Resolvers 是 Relay 在客户端定义字段的核心能力而错误处理是让客户端字段与 GraphQL 服务端字段行为保持一致的关键机制。本文以 errors.md 为主体深入讲解 Relay 如何捕获 Resolver 抛出的错误、如何通过relayFieldLogger记录relay_resolver.error事件以及如何借助semanticNonNull表达语义非空但客户端仍需容错的字段。读完本文你将能实现一个完整的 Resolver 错误日志方案并正确设计带错误容错的非空字段类型。Relay Resolvers 错误处理的设计理念与 GraphQL 服务端一致Relay Resolvers 支持字段级field-level错误处理。核心规则只有一条如果某个 Resolver 在读取字段时抛出错误Relay 会将该错误记录到环境Environment提供的relayFieldLogger中同时该字段的读取结果会变为null。这条规则提供了一种与服务端 GraphQL 的对称性symmetry在服务端GraphQL 规范允许字段返回错误并在响应中携带errors字段本身变为null可空字段场景在客户端Resolver 抛出的 JS 异常被 Relay 捕获后同样被折叠为null并通过日志机制暴露给开发者。这种设计让团队可以先用客户端 Relay Resolvers 实现字段后续再平滑地把字段迁移到服务端——因为两端的错误语义是一致的迁移时上层组件代码无需改变对字段可能为 null的假设。为了让上述行为在运行时成立Relay 编译器不允许 Resolver 字段的类型被声明为非空non-nullable。也就是说你在 docblock 中声明的 Resolver 返回类型不能是String!这类非空形式因为一旦字段类型被类型系统声明为非空运行时却可能因为异常返回null就会造成类型系统与运行时行为的不一致。这是编译器层面的强制约束源码实现位于 compiler/crates/relay-transforms 的 Resolver 转换与校验逻辑中。错误事件的形状relay_resolver.error当 Resolver 抛出错误时传递给relayFieldLogger的事件对象具有如下结构type ResolverErrorEvent { kind: relay_resolver.error, // The name of the fragment/query in which the field was read owner: string, // The path from the owner root to the field which threw the error fieldPath: string, // The error thrown by the resolver error: Error, }字段含义说明字段含义kind事件类型标识固定为relay_resolver.error用于日志器快速分发owner读取该字段时所在的 fragment / query 名称即宿主操作名fieldPath从 owner 根节点到抛出错误的字段之间的完整路径如me.required_nameerrorResolver 实际抛出的Error对象包含原始错误信息在 v19 仓库的运行时实现中该事件被定义在 packages/relay-runtime/store/RelayStoreTypes.js 的RelayResolverErrorEvent类型里。相比文档示例源码中的事件类型还额外携带了三个字段shouldThrow: boolean表示宿主查询/fragment 是否标注了throwOnFieldErrorhandled: boolean表示该错误是否已被catch指令或 Resolver 返回null所处理uiContext: unknown | void由 React Hooks 层在日志时填充的 UI 上下文。这些字段服务于 Relay 更进阶的错误处理能力throwOnFieldError与catch指令也是事件被消费时决定是仅记录还是抛出异常的依据。从抛错到日志的运行时链路要真正理解relay_resolver.error事件的生命周期需要看两条关键代码路径1. 错误在 RelayReader 中被记录Resolver 在读取过程中抛出的错误由 packages/relay-runtime/store/RelayReader.js 捕获并构建为事件对象。源码中可以看到读取器维护一个_fieldErrors数组把本次读取中所有 Resolver 错误聚合并挂到 snapshot 上事件中的owner来自this._fragmentName当前读取的 fragment 名fieldPath记录从 owner 根到出错字段的路径若宿主选择集标注了throwOnFieldError则shouldThrow置为true。这段逻辑还处理了缓存命中场景从 store 缓存读取快照时先前记录的错误会被原样恢复RelayReader.js从而保证重复读取仍能稳定上报错误。2. 事件在 snapshot 消费阶段被送达日志器事件真正传递给relayFieldLogger的地方是 packages/relay-runtime/util/handlePotentialSnapshotErrors.js。handleFieldErrors会遍历 snapshot 上的所有字段错误依次调用environment.relayFieldLogger({ ...fieldError, uiContext: loggingContext, });注意两点实现细节读取器RelayReader构造事件时uiContext恒为undefined由 Hooks 层传入的loggingContext在此处统一填充日志器总是先被调用之后才根据shouldThrow与handled决定是否抛出异常。也就是说即使最终要抛错错误也已经先被记录过一轮方便追踪。若事件需要抛出shouldThrow true且handled false抛出信息会拼接为Relay: Resolver error at path fieldPath in owner. Message: error.message编写一个relayFieldLogger示例在 errors.md 中给出了一个可直接使用的完整示例——把日志器注入Environment即可捕获所有 Resolver 错误function fieldLogger(event) { if(event.kind relay_resolver.error) { // Log this somewhere! console.warn(Resolver error encountered in ${event.owner}.${event.fieldPath}) console.warn(event.error) } } const environment new Environment({ network: Network.create(/* your fetch function here */), store: new RelayModernStore(new RecordSource()), relayFieldLogger: fieldLogger });实践要点relayFieldLogger是Environment构造器的一个可选选项类型定义见 packages/relay-runtime/store/RelayStoreTypes.js。一个环境通常对应一个应用根日志器在整个环境生命周期内对所有 snapshot 读取生效日志器会收到多种事件relay_resolver.error、relay_field_payload.error、missing_required_field.*等务必先按kind分发再针对性处理日志器内部可以接入任何错误上报基础设施Sentry、内部监控平台等把owner与fieldPath拼接后可作为聚合维度方便定位哪个 fragment 的哪个路径最容易出错日志器也可以选择主动抛出自己的错误——源码注释明确指出logger may opt to throw its own error here if it wants to throw an error that is better integrated into sites error handling infrastructure。如果希望抛错请自行负责让错误进入你的错误处理体系避免被 Relay 的默认抛错路径接管。Live Resolvers 的错误处理文档特别提示了一个常见场景Live Resolvers实时 Resolver既可能在首次求值时抛错也可能在其.read()方法被调用时抛错。:::note Live Resolvers can potentially throw errors when they are first evaluated or when their .read() method is called. Both types of errors will be handled identically by Relay. :::这意味着无论是 Resolver 函数体首次执行初始化 LiveState时的同步异常还是后续.read()读取最新实时值时抛出的异常Relay 都会以完全相同的方式处理——记入relayFieldLogger字段值为null因此为 Live Resolvers 编写错误处理时无需区分错误发生的阶段可以共用同一套relayFieldLogger逻辑。对应地仓库测试 packages/relay-runtime/store/tests/resolvers/LiveResolvers-test.js 中对 Live Resolvers 抛出错误后产生relay_resolver.error事件、字段变为null的行为做了覆盖验证可作为参考用例。测试验证错误事件的行为断言在 packages/relay-runtime/store/tests/RelayReader-Resolver-test.js 中可以看到 Relay 官方对 Resolver 错误事件行为的具体断言。测试读取一个包含required_nameResolver 字段的查询后期望store.lookup()返回的fieldErrors中包含{ error: expect.anything(), fieldPath: me.required_name, handled: false, kind: relay_resolver.error, owner: RelayReaderResolverTestRequiredQuery, shouldThrow: false, }且该测试在第二次从缓存读取时依然断言得到相同的错误事件验证了错误信息在缓存快照中会被保留并重复上报。这个测试从侧面印证了本文前面描述的运行时行为错误被收集到 snapshot 的fieldErrors中供日志器消费且默认无throwOnFieldError时shouldThrow为false即只记录、不抛出。与throwOnFieldError/catch的协同虽然本文档聚焦记录并返回 null的默认行为但从运行时源码可以看出错误事件还会与两个进阶指令协同throwOnFieldError标注在查询/fragment 上后读取时若遇到字段错误包括 Resolver 错误Relay 会在记录日志后直接抛出异常。此时shouldThrow为truehandlePotentialSnapshotErrors会抛出Relay: Resolver error at path ...错误见 handlePotentialSnapshotErrors.js。使用该指令时应用必须在上方组件配置 React 错误边界 来捕获这些异常catch显式捕获字段错误后事件会被标记为handled: true从而不再触发默认抛错路径仅保留日志记录。这两个指令共同构成了 Relay 完整的字段级错误策略而relayFieldLogger始终是第一道记录防线。支持语义可空性Semantic Nullability除了默认的错误即 null之外Relay Resolver 字段还支持像服务端 schema 字段一样标注语义非空semantically non-null。语义可空性的核心思想是某些字段在业务语义上永远不应为 null例如用户的name只有在出错时才会以 null/错误形式出现。为了不牺牲类型安全开发者在 docblock 中为 Resolver 字段添加semanticNonNull指令表达字段语义上非空但客户端仍需准备处理错误。在 Relay Resolver 的 docblock 中标注方式如下取自原文档完整示例/** * RelayResolver RelayExample.semantic_non_null_field: String semanticNonNull */ export function semantic_non_null_field( model: RelayExampleModel, ): string { return model.someField ?? field was null, this is the default; }要点解读docblock 声明行中类型String之后紧跟semanticNonNull指令函数实现仍然返回普通的string类型但运行时字段值可能因错误为null该示例还演示了防御性兜底的常见写法当底层字段为 null 时返回一个默认字符串避免 null 传播。关于该指令的更完整背景schema 侧声明方式可以继续阅读 semantic-nullability.mddirective semanticNonNull(levels: [Int] [0]) on FIELD_DEFINITION type User { name: String semanticNonNull }在 v19 仓库中semanticNonNull与throwOnFieldError的配合规则也在编译器校验中有体现例如 compiler/crates/relay-transforms/src/errors.rs 会拒绝在throwOnFieldError或catch选择集中的semanticNonNull字段上再添加required指令——因为语义非空字段本身就是非空语义叠加required属于冗余。总结与实践清单Relay Resolvers 的错误处理机制可以归纳为一条简洁的心智模型Resolver 抛错 → 字段变 null → 事件进入relayFieldLogger这三步是默认且自动的编译器强制Resolver 字段不能被声明为非空为运行时折叠为 null留出余地日志事件relay_resolver.error携带owner、fieldPath、error等结构化信息可用于聚合监控Live Resolvers的初始化错误与.read()错误处理方式一致进阶组合throwOnFieldError出错即抛出需要错误边界配合、catch显式捕获标记handled与semanticNonNull语义非空但运行时仍可容错。动手实践建议按以下顺序验证在Environment构造时传入一个relayFieldLogger按kind relay_resolver.error分发并上报owner、fieldPath、error写一个会抛异常的 Resolver 字段观察字段渲染为null且日志器收到事件参考 RelayReader-Resolver-test.js 中的断言用store.lookup()检查fieldErrors确认缓存命中时错误仍会重现若字段业务上非空但可能出错使用semanticNonNull标注并按需在 fragment 上添加throwOnFieldError与错误边界。进一步阅读同一指南的其他章节可以继续查看 defining-fields.md如何定义 Resolver 字段、live-fields.mdLive Resolvers 详解以及 throw-on-field-error-directive.mdthrowOnFieldError指令。赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐Relay Resolvers 错误处理机制完全指南relayFieldLogger、字段置空与编译期约束Relay Resolvers 错误处理机制完全指南relayFieldLogger、字段置空与编译期约束 导读 本文以 Relay 17 版本官方文档《Er前端开发工具Relay Resolver 错误处理完全指南字段级错误日志、Null 兜底与 semanticNonNull 语义非空Relay Resolver 错误处理完全指南字段级错误日志、Null 兜底与 semanticNonNull 语义非空 Relay Resolver 允许前端开发工具Authelia CLI 实战用 storage user totp export png 将 TOTP 配置批量导出为二维码 PNGAuthelia CLI 实战用 storage user totp export png 将 TOTP 配置批量导出为二维码 PNG 本篇指南基于 Auth前端开发工具上一篇Gofile开源下载工具高效解决文件下载难题的完整方案下一篇7个Gofile下载难题终极解决方案快速免费下载工具完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考