- 后端
【免费下载链接】graffle
Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.
导读
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.envelope | boolean \| { 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.
相关推荐
免费的 Jellyfin 桌面播放器:告别浏览器卡顿,5 分钟跑起家庭影院
免费的 Jellyfin 桌面播放器:告别浏览器卡顿,5 分钟跑起家庭影院 Jellyfin Desktop 是一款免费开源的桌面播放器,它把 Jellyfin
后端Graffle 配置指南:在 Envelope 输出模式下抛出错误(output.envelope.errors 详解)
Graffle 配置指南:在 Envelope 输出模式下抛出错误(output.envelope.errors 详解) 本文讲解 Graffle GraphQ
后端Graffle 输出配置实战:用 Preset.traditionalGraphqlOutput 还原传统 GraphQL ExecutionResult 行为
Graffle 输出配置实战:用 Preset.traditionalGraphqlOutput 还原传统 GraphQL ExecutionResult 行为
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考