☰
Graffle 输出配置实战:使用 errorChannel 让 GraphQL 错误以返回值代替异常抛出
2026/10/10 1:19:31 网站建设 项目流程
  • 后端

【免费下载链接】graffle

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

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

导读

Graffle 是一个极简、可扩展且类型安全的 JavaScript GraphQL 客户端。默认情况下,Graffle 会把 GraphQL 执行错误与扩展层错误统一以异常(throw)的形式抛出;而在很多业务场景中,开发者更希望错误像"普通返回值"一样参与流程控制。本文围绕 Graffle 官方示例 return-error 展开,讲解如何通过output.defaults.errorChannel: 'return'将错误改为返回,并深入output.errors细粒度配置、类型系统如何同步收窄返回值,以及源码层面的错误分发原理,帮助你写出更可控的错误处理代码。

默认行为:错误一律抛出

在了解return之前,先明确 Graffle 的默认输出行为。直接调用Graffle.create()而不传任何配置时,客户端会使用默认输出设置:

const pokemon = Graffle.create() const pokemons = await pokemon.query.pokemons({ name: true })

对应官方示例 default 及 examples/20_output/output_default.ts。此时若请求出现错误(例如发送空查询、执行错误或扩展抛错),请求会以异常形式结束。

这一默认行为在源码配置中有明确体现:在 output 配置片段 中,defaults.errorChannel的默认值为'throw':

const default_ = { defaults: { errorChannel: `throw`, }, ... }

同时,测试 with_output.test.ts 也用内联快照验证了默认抛错行为:

test('default is throws errors', async () => { await expect(g1.gql('').$send()).rejects.toThrowErrorMatchingInlineSnapshot( `[ContextualAggregateError]`, ) })

核心配置:output.defaults.errorChannel: 'return'

要让错误从"抛出"变为"返回",只需在创建客户端时配置output.defaults.errorChannel。官方示例 return-error 给出的完整代码如下:

import { Graffle } from './graffle/_.js' const pokemon = Graffle .create({ output: { envelope: false, defaults: { errorChannel: `return`, }, }, }) .anyware(({ encode: _ }) => { throw new Error(`Something went wrong.`) }) const pokemons = await pokemon.query.pokemons({ name: true }) console.log(pokemons)

该示例对应的可运行源码位于 examples/20_output/output_return-error.ts。可以看到,示例通过.anyware()在encode钩子中主动抛出一个错误,用于模拟"其他类错误"(other error,例如扩展抛出的错误、传输层网络错误等)。

关键点在于:即使拦截器内部throw了错误,配置了errorChannel: 'return'之后,pokemon.query.pokemons(...)调用不会抛出异常,而是把错误作为函数的返回值交还给调用方,随后console.log(pokemons)正常打印出错误对象。

示例运行后的实际输出(来源 output_return-error.snap):

ContextualError: There was an error in the interceptor "anonymous" (use named functions to improve this error message) while running hook "encode". at runPipeline (/some/path/to/runPipeline.ts:XX:XX) at async <anonymous> (/some/path/to/runner.ts:XX:XX) at async Module.run (/some/path/to/run.ts:XX:XX) at async sendRequest (/some/path/to/send.ts:XX:XX) at async executeRootField (/some/path/to/requestMethods.ts:XX:XX) at async <anonymous> (/some/path/to/output_return-error.ts:XX:XX) { context: { hookName: 'encode', source: 'extension', interceptorName: 'anonymous' }, cause: Error: Something went wrong. at <anonymous> (/some/path/to/output_return-error.ts:XX:XX) at applyBody (/some/path/to/runner.ts:XX:XX) }

这个返回的错误是ContextualError类型,它携带了非常丰富的诊断信息:

  • context字段记录了hookName: 'encode'、source: 'extension'与interceptorName,说明错误发生在请求管线的哪个环节;
  • cause字段保留了原始的Error: Something went wrong.,不丢失根因;
  • 错误消息提示"use named functions to improve this error message"——若希望错误信息更友好,可以给拦截器命名函数而不是匿名函数。

配置项全解:output片段支持哪些参数

结合 configuration.ts 中的Input接口,output配置完整支持以下参数:

配置路径类型默认值说明
output.defaults.errorChannel'throw' \| 'return''throw'全局默认错误通道:抛出或返回
output.envelopeboolean \| { enabled?, errors?: { execution?, other? } }false是否启用信封输出(data/errors/extensions结构)
output.errors.execution'throw' \| 'return' \| 'default''default'执行错误的通道,'default'表示跟随defaults.errorChannel
output.errors.other'throw' \| 'return' \| 'default''default'其他错误(网络、扩展等)的通道,'default'表示跟随defaults.errorChannel

其中两类错误的官方定义(源码注释)为:

  • execution 错误:传统上出现在 GraphQL 执行结果errors字段中的错误,例如字段校验失败、参数不合法等;
  • other 错误:包括 HTTP 传输时fetch抛出的网络错误、扩展(extension)抛出的错误等。

'default'的解析逻辑由readErrorCategoryOutputChannel实现(configuration.ts):

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

也就是说,errors.execution与errors.other的'default'取值会在运行时被解析为defaults.errorChannel的实际值。这正是"全局默认 + 按类覆盖"两级配置模型的实现基础。

类型系统同步:返回值类型自动包含错误联合

errorChannel: 'return'的另一个优势在于类型层面的一致性。在 handle.ts 的类型定义中,IfConfiguredGetOutputErrorReturns会根据配置把错误类型并入返回类型:

type IfConfiguredGetOutputErrorReturns<$OutputConfig extends Normalized> = | (ConfigGetOutputError<$OutputConfig, 'execution'> extends 'return' ? GraphqlKit.Request.GraphQLExecutionResultError : never) | (ConfigGetOutputError<$OutputConfig, 'other'> extends 'return' ? Ware.ResultFailure : never)

这意味着启用errorChannel: 'return'后,await pokemon.query.pokemons(...)的静态类型会自动变为"正常数据 | 执行错误 | 其他错误"的联合类型。TypeScript 编译器会强制你处理错误分支,从源头杜绝"忘记 catch"的问题。

注释掉的测试用例也印证了这一点(with_output.test.ts):

// const g = G({ output: { defaults: { errorChannel: 'return' } }, checkPreflight: false }) // test('query.<fieldMethod>', async () => { // expectTypeOf(await g.query.__typename()).toEqualTypeOf< // 'Query' | Ware.ResultFailure | GraphQLExecutionResultError // >() // })

细粒度控制:errors.execution与errors.other分开配置

如果业务上希望"执行错误返回、其他错误抛出"(或反之),可以使用errors参数覆盖默认通道。官方姊妹示例 return-error-execution 及其源码 output_return-error_return-error-execution__return-error-execution.ts 展示了这种场景:

const pokemon = Graffle .create({ output: { envelope: false, errors: { execution: `return`, other: `throw`, }, }, }) // 1. 执行错误(空的 Pokemon name)会被返回 const result = await pokemon.mutation.addPokemon({ $: { name: ``, hp: 1, defense: 0, attack: 0, $type: `water` }, name: true, }) console.log(result) // 2. 其他错误(此处来自内联扩展)会被抛出 try { await pokemon .anyware(({ encode: _ }) => { throw new Error(`Something went wrong.`) }) .query .pokemons({ name: true }) } catch (error) { console.log(error) }

在该示例中,addPokemon因为空名称触发的执行错误(ContextualAggregateError,包含too_small、Pokemon name cannot be empty.等结构化信息)会被返回;而.anyware()内联扩展抛出的错误则依旧抛出。这样就能针对不同错误来源制定不同的处理策略。

测试 with_output.test.ts 也覆盖了这种组合配置的类型推导:

// .execution: 'throw' // expectTypeOf(await g.query.__typename()).toEqualTypeOf<'Query' | Ware.ResultFailure>() // .other: 'throw' // expectTypeOf(await g.query.__typename()).toEqualTypeOf<'Query' | GraphQLExecutionResultError>()

即:把某类错误设为'throw'后,该类错误会从返回类型联合中消失,类型层面与运行时行为保持同步。

底层原理:handleOutput如何分发错误

errorChannel的运行时语义最终由 handle.ts 中的handleOutput函数实现。它会读取规范化后的输出配置,逐类判断错误的处理方式:

const isThrowOther = readErrorCategoryOutputChannel(c, `other`) === `throw` && (!c.envelope.enabled || !c.envelope.errors.other) const isReturnOther = readErrorCategoryOutputChannel(c, `other`) === `return` && (!c.envelope.enabled || !c.envelope.errors.other) const isThrowExecution = readErrorCategoryOutputChannel(c, `execution`) === `throw` && (!c.envelope.enabled || !c.envelope.errors.execution) const isReturnExecution = readErrorCategoryOutputChannel(c, `execution`) === `return` && (!c.envelope.enabled || !c.envelope.errors.execution)

随后针对两类错误分别处理:

  • 当管线输出是Error实例(即 other 类错误,如拦截器抛错)时:isThrowOther则throw result,isReturnOther则return result;
  • 当执行结果result.value.errors非空(execution 类错误)时:先包装成Err.ContextualAggregateError,再依据isThrowExecution/isReturnExecution决定抛出或返回;
  • 正常数据则返回result.value.data(未启用信封时)或完整的执行结果信封。

从这段实现可以推断:errorChannel与envelope(信封输出)是互相影响的两个维度——当信封启用且对应错误类别被收进信封时,错误会放入信封的errors字段而非直接返回/抛出。配置默认值中envelope.enabled为false、errors.execution/other均为'default',因此开箱即用行为是"执行错误与其他错误全部跟随defaults.errorChannel"。

实战建议与注意事项

  • 优先使用命名拦截器:匿名拦截器抛错时,返回的ContextualError消息会提示"use named functions to improve this error message"。为.anyware()的拦截器命名,可让诊断信息更可读。
  • 结合envelope使用:若同时开启output.envelope: true且希望错误进入信封,可参考 envelope 示例,并通过envelope.errors.execution / other控制哪些错误收进信封。
  • 善用类型驱动:errorChannel: 'return'会把错误类型并入返回值的联合类型,配合 TypeScript 的穷尽检查,可以确保每条请求路径都显式处理错误分支。
  • 配置来源多样:output配置既可在Graffle.create()中一次性声明,也可通过.with()方法在既有客户端实例上增量覆盖(如测试 with_output.test.ts 中的g1.with({ output: { defaults: { errorChannel: 'return' } } })),方便针对不同调用场景切换错误策略。

小结

Graffle 的output配置提供了一条从"异常驱动"到"返回值驱动"的平滑路径:defaults.errorChannel: 'return'一键切换全局错误通道;errors.execution / other实现对执行错误与其他错误的分流;类型系统同步收窄返回类型,让错误分支在编译期即可被感知。配合handleOutput的运行时分发与ContextualError/ContextualAggregateError的丰富诊断上下文,你可以为不同的 GraphQL 请求场景定制一致且可预期的错误处理体验。进一步阅读可在仓库中查看 output 配置片段、输出处理实现 以及 输出相关官方示例。

  • 后端

【免费下载链接】graffle

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

项目地址:https://gitcode.com/gh_mirrors/gr/graffle
点击查看免费下载
上一篇:请求日志分析器(Request Log Analyzer)安装与使用指南
下一篇:WidescreenFixesPack完整指南:如何为100+游戏添加宽屏分辨率支持

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

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

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

立即咨询