☰
Graffle 错误输出通道精细配置:按错误类别分别使用 return 与 throw
2026/10/10 2:35:01 网站建设 项目流程
  • 后端

【免费下载链接】graffle

Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.

项目地址:https://gitcode.com/gh_mirrors/gr/graffle
点击查看免费下载

Graffle(Simple GraphQL Client for JavaScript)允许你在Graffle.create({ output })中按错误类别(execution与other)分别指定错误通道(return或throw),从而在同一客户端中实现"部分错误返回、部分错误抛出"的混合策略。本文以仓库中的 "Return Error Execution" 示例(website/content/examples/20_output/return-error-execution.md)为主线,结合输出配置的源码实现与底层处理逻辑,讲解如何配置、如何理解返回与抛出的错误对象,以及它与defaults.errorChannel、envelope等输出配置项的关系。

1. 示例要解决的核心问题

GraphQL 请求过程中可能产生两类性质截然不同的错误:

  • execution(执行类错误):即传统上出现在 GraphQL 执行结果errors字段中的错误,例如服务端对字段的校验失败、查询执行出错。本例中空字符串的 Pokemon 名字触发的too_small校验错误即属此类。
  • other(其他错误):包括 HTTP 传输层 fetch 抛出的网络错误、扩展(extension)或拦截器(anyware interceptor)内部抛出的错误等。本例中通过.anyware()拦截器主动抛出的Error('Something went wrong.')即属此类。

不同类别的错误适合不同的处理方式:执行错误往往携带结构化信息(如错误码、路径),适合作为返回值交给业务层分支处理;而拦截器/扩展错误通常代表客户端内部故障,直接抛出更符合直觉。Graffle 的output.errors配置正是为此设计的。

完整示例代码见 examples/20_output/output_return-error_return-error-execution__return-error-execution.ts,其实际运行输出快照见 examples/tests/snapshots/20_output/output_return-error_return-error-execution__return-error-execution.snap。

2. 核心配置:output.errors按类别指定通道

示例通过Graffle.create传入如下配置:

import { Graffle } from './graffle/_.js' const pokemon = Graffle .create({ output: { envelope: false, errors: { execution: `return`, other: `throw`, }, }, })

配置含义一目了然:

  • output.errors.execution: 'return'——执行类错误返回给调用方;
  • output.errors.other: 'throw'——其他类错误抛出给调用方;
  • output.envelope: false——不使用信封(envelope)包裹结果,函数直接返回数据本身或错误对象。

从 src/context/fragments/configuration/output/configuration.ts 的源码可以看到该配置的完整类型定义:

export type OutputChannel = 'throw' | 'return' export type OutputChannelConfig = 'throw' | 'return' | 'default' export type ErrorCategory = 'execution' | 'other' export interface Input { defaults?: { /** @defaultValue `'throw'` */ errorChannel?: OutputChannel } /** @defaultValue `false` */ envelope?: boolean | ConfigurationInputOutputEnvelopeLonghand errors?: { execution?: OutputChannelConfig other?: OutputChannelConfig } }

关键点:

  • errors.execution/errors.other除'throw'、'return'外,还支持'default',表示跟随defaults.errorChannel(其默认值为'throw');
  • 未配置errors时,两类错误都落到defaults.errorChannel的默认值'throw'上,即默认行为是全部抛出;
  • envelope默认false,即默认不返回信封结构。

3. 逐段解读示例:返回执行错误、抛出其他错误

3.1 执行错误被返回

// 1. The __execution__ error of an empty Pokemon name will be ***returned***. const result = await pokemon.mutation.addPokemon({ $: { name: ``, hp: 1, defense: 0, attack: 0, $type: `water` }, // ^^ name: true, })

这里向addPokemon传入空字符串name,服务端返回执行错误(错误码too_small,消息 "Pokemon name cannot be empty.")。由于配置了execution: 'return',result不是被抛出,而是被返回——它是一个ContextualAggregateError,结构如下(来自示例文档的 Outputs 部分与快照):

ContextualAggregateError errors: [ ContextualError: [ { "code": "too_small", "minimum": 1, "type": "string", "inclusive": true, "exact": false, "message": "Pokemon name cannot be empty.", "path": [ "name" ] } ] ... context: { locations: [ { line: 2, column: 3 } ], path: [ 'addPokemon' ] }, _tag: 'ContextualError' ], context: {}, _tag: 'ContextualAggregateError'

其中每个执行错误都被包装为ContextualError,携带context(如 GraphQL 中的locations与path)与原始校验细节(code、minimum、path等),并聚合成ContextualAggregateError。业务代码可以安全地检查返回值,而无需 try/catch 包裹。

3.2 其他错误被抛出

// 2. The __other__ error, in this case from the inline extension, will be ***thrown***. try { await pokemon .anyware(({ encode: _ }) => { throw new Error(`Something went wrong.`) }) .query .pokemons({ name: true }) } catch (error) { show(error) }

内联 anyware 拦截器在encode钩子阶段抛出Error('Something went wrong.')。由于配置了other: 'throw',该错误被抛出并进入catch分支。抛出的错误对象是带有上下文的ContextualError:

ContextualError: There was an error in the interceptor "anonymous" (use named functions to improve this error message) while running hook "encode". context: { hookName: 'encode', source: 'extension', interceptorName: 'anonymous' }, cause: Error: Something went wrong.

可以看到 Graffle 为拦截器错误附加了hookName、source、interceptorName等诊断上下文,并把原始Error保留在cause中。错误信息还提示"使用命名函数可以改善此错误信息"——即在.anyware()中传入命名函数便于定位具体拦截器。

4. 底层实现:handleOutput如何决定返回还是抛出

返回/抛出的决策最终由 src/client/handle.ts 中的handleOutput完成。其核心逻辑先用readErrorCategoryOutputChannel解析每个类别的最终通道:

export const readErrorCategoryOutputChannel = ( output: Normalized, errorCategory: ErrorCategory, ): OutputChannel | false => { if (output.errors[errorCategory] === `default`) { return output.defaults.errorChannel } return output.errors[errorCategory] }

即:当errors[errorCategory]为'default'时回落到defaults.errorChannel,否则直接采用配置值。随后handleOutput依据类别与通道分派:

const isThrowOther = readErrorCategoryOutputChannel(c, `other`) === `throw` && (!c.envelope.enabled || !c.envelope.errors.other) const isReturnExecution = readErrorCategoryOutputChannel(c, `execution`) === `return` && (!c.envelope.enabled || !c.envelope.errors.execution) if (result instanceof Error) { if (isThrowOther) throw result if (isReturnOther) return result return isEnvelope ? { errors: [result] } : result } if (result.value.errors && result.value.errors.length > 0) { const error = new Err.ContextualAggregateError({ ... }) if (isThrowExecution) throw error if (isReturnExecution) return error return isEnvelope ? { ...result.value, errors: [...] } : error }

从源码结构可以确认两点实现事实:

  1. 执行错误(result.value.errors非空)先聚合为ContextualAggregateError,再按execution通道决定throw或return;
  2. 其他错误(result instanceof Error,如拦截器错误、传输错误)直接按other通道决定throw或return。

此外,类型层面HandleOutput_ErrorsReturn也会根据配置推导返回类型:当某类别配置为'return'时,函数返回值类型会并入对应的错误类型(执行错误为GraphQLExecutionResultError,其他错误为Ware.ResultFailure),从而在编译期就保证开发者处理了可能返回的错误。

5. 与相邻配置的关系:defaults.errorChannel与envelope

5.1defaults.errorChannel:全局默认通道

示例文档 examples/20_output/output_return-error.ts 展示了另一种写法:不按类别细分,而是统一设置默认错误通道:

const pokemon = Graffle .create({ output: { envelope: false, defaults: { errorChannel: `return`, }, }, })

此时两类错误都走'return'。若同时配置defaults.errorChannel与errors.execution/other,则细粒度配置优先,未指定的类别回落到默认通道——这正是OutputChannelConfig中包含'default'的原因。

5.2envelope:错误嵌入信封

若开启envelope(如 website/content/examples/20_output/envelope.md 与 website/content/examples/20_output/envelope-error.md 所示),函数返回{ data, errors, extensions }结构;envelope.errors.execution/other(默认execution: true、other: false)控制哪类错误被嵌入信封而非返回/抛出。注意handleOutput中所有通道判断都带有(!c.envelope.enabled || !c.envelope.errors.xxx)条件,说明当某类错误被配置为嵌入信封时,该类别不再走 return/throw 通道,而是留在信封的errors字段中。

6. 运行方式与适用场景

该示例属于仓库examples/20_output/目录下可运行示例之一。按 website/content/examples/index.md 的说明,本地运行此类示例可以使用:

npx graffle try return-error-return-error-execution

示例依赖一个本地 Pokemon GraphQL Schema(可先运行npx graphql-try pokemon启动),且部分示例需要先执行pnpm graffle generate --schema http://localhost:4000/graphql生成客户端(生成后的模块位于examples/$/graffle/)。

适用场景总结:

  • 执行类错误(业务校验、服务端执行失败)→ 配置execution: 'return',在业务层用类型守卫与分支逻辑优雅处理,避免大量 try/catch;
  • 其他错误(网络、扩展、拦截器)→ 配置other: 'throw',让其作为异常快速失败,便于集中上报与排查;
  • 全部返回或全部抛出 → 分别使用defaults.errorChannel: 'return'或保持默认('throw');
  • 需要兼容传统 GraphQLExecutionResult结构 → 开启envelope,并按需配置envelope.errors.execution/other决定哪些错误嵌入信封。

7. 小结

Graffle 的output.errors配置把"错误如何呈现"从"一刀切"升级为按类别精细路由:execution与other两类错误可各自独立地return、throw或跟随default默认通道。本文示例(website/content/examples/20_output/return-error-execution.md)展示的正是"执行错误返回、其他错误抛出"的混合策略,其决策链路从 配置定义 到 运行时处理 清晰可循,且返回/抛出的错误对象(ContextualAggregateError/ContextualError)均携带结构化上下文,便于下游消费与诊断。

  • 后端

【免费下载链接】graffle

Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.

项目地址:https://gitcode.com/gh_mirrors/gr/graffle
点击查看免费下载
上一篇:ipatool:3 条命令完成 IPA 下载
下一篇:CursorCode 插件怎么用:5 分钟上手 VSCode 里的 AI 代码助手

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询