- 后端
【免费下载链接】graffle
Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.
本篇指南讲解 Graffle(Simple GraphQL Client for JavaScript)中output.envelope输出模式下的错误通道配置。你将掌握如何通过envelope.errors.execution与envelope.errors.other两个开关,决定执行错误与“其他错误”(如网络错误、扩展抛出的错误)是嵌入信封(envelope)的errors字段,还是直接抛出异常;并结合 Graffle 源码理解这一决策在运行时与类型层面的完整实现路径。
示例场景:开启信封,但依然抛错
Graffle 的 envelope 输出模式会把结果包装为{ data, errors, extensions }结构。默认情况下,当错误类别对应的开关为true时,错误会被放入 envelope 的errors数组中而非抛出;而本示例(website/content/examples/20_output/envelope-error-throw.md)演示的则是相反的场景:即使启用了 envelope,也强制让错误以异常形式抛出。
完整示例代码(examples/20_output/output_envelope_envelope_error-throw__envelope-error-throw.ts):
import { Graffle } from '../$/graffle/_.js' // dprint-ignore const pokemon = Graffle .create({ output: { envelope: { errors: { execution: false, other: false, // default } }, }, }) .anyware(({ encode: _ }) => { throw new Error(`Something went wrong.`) //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ }) await pokemon.query.pokemons({ name: true })运行后输出(实际运行产物见 examples/outputs/20_output/output_envelope_envelope_error-throw__envelope-error-throw.output.txt):
/some/path/to/runPipeline.ts:XX return new ContextualError(message, { ^ 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_envelope_envelope_error-throw__envelope-error-throw.ts:XX:XX) { context: { hookName: 'encode', source: 'extension', interceptorName: 'anonymous' }, cause: Error: Something went wrong. at <anonymous> (/some/path/to/output_envelope_envelope_error-throw__envelope-error-throw.ts:XX:XX) at applyBody (/some/path/to/runner.ts:XX:XX) } Node.js vXX.XX.XX可以看到,await pokemon.query.pokemons(...)并没有返回 envelope 对象,而是直接抛出了ContextualError——它携带了原始错误(cause: Error: Something went wrong.)以及丰富的错误上下文(hookName、source、interceptorName)。
envelope.errors 两个开关的含义与默认值
output.envelope.errors是长格式(longhand)配置,控制两类错误的去向,其类型定义位于 src/context/fragments/configuration/output/configuration.ts:
| 配置项 | 含义 | 默认值 |
|---|---|---|
envelope.errors.execution | 执行错误:即传统 GraphQL 执行结果中errors字段里的错误(如字段解析失败、校验错误) | true(嵌入 envelope) |
envelope.errors.other | 其他错误:包括 HTTP 传输层 fetch 抛出的网络错误、扩展(extension)内抛出的错误等 | false(不嵌入,走默认通道) |
其归一化默认值在源码default_对象中(configuration.ts):
const default_ = { defaults: { errorChannel: `throw`, // 默认错误通道:抛出 }, envelope: { enabled: false, // 信封默认关闭 errors: { execution: true, // 执行错误默认嵌入信封 other: false, // 其他错误默认不嵌入 }, }, errors: { execution: `default`, other: `default`, }, }注意这里的两层设计:
errors.execution / errors.other(顶层)可显式指定'throw' | 'return' | 'default','default'表示回落到defaults.errorChannel(默认为'throw')。envelope.errors.execution / other(布尔)控制是否把对应类别错误嵌入信封;当其为false时,该类别错误不进入信封,转而按顶层错误通道处理。
本示例将两者都设为false,于是扩展抛出的错误既不进信封,又因默认通道是throw而直接抛出。
对比:嵌入信封的版本
为了看清“抛出”与“嵌入”的区别,可对比姊妹示例 website/content/examples/20_output/envelope-error.md 及其源码 examples/20_output/output_envelope_envelope-error__envelope-error.ts。它只把两个开关改为true:
const pokemon = Graffle .create({ output: { envelope: { errors: { execution: true, // default other: true, }, }, }, }) .anyware(({ encode: _ }) => { throw new Error(`Something went wrong.`) }) const result = await pokemon.query.pokemons({ name: true }) console.log(result)此时同样的错误不再抛出,而是出现在返回结果的errors数组中(见 examples/outputs/20_output/output_envelope_envelope-error__envelope-error.output.txt):
{ errors: [ 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. ... } ] }两张输出对比一目了然:同一处throw new Error(...),开关为true时错误被俘获进信封;开关为false时错误被原样抛出。这正是 Graffle 输出配置“细粒度控制”能力的体现。
源码解析:错误通道是如何被决策的
运行时:readErrorCategoryOutputChannel 与 handleOutput
错误最终的走向由客户端输出处理函数handleOutput决定(src/client/handle.ts)。它先从配置读取每个错误类别的实际通道:
export const readErrorCategoryOutputChannel = ( output: Normalized, errorCategory: ErrorCategory, ): OutputChannel | false => { if (output.errors[errorCategory] === `default`) { return output.defaults.errorChannel } return output.errors[errorCategory] }(见 configuration.ts)——即'default'回落为defaults.errorChannel。
随后handleOutput依据“是否启用信封 + 该类别是否嵌入信封”组合出四个布尔判断(handle.ts):
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)对“其他错误”(本示例由 anyware 扩展在encode钩子抛出)而言,当结果为Error时:
if (result instanceof Error) { if (isThrowOther) throw result if (isReturnOther) return result return isEnvelope ? { errors: [result] } : result }在本示例配置(envelope.enabled为真、envelope.errors.other为false、默认通道为throw)下,isThrowOther === true,于是throw result——错误直接抛出,封装为ContextualError。ContextualError来自@wollybeard/kit的Err命名空间(handle.ts 中同类错误亦用new Err.ContextualError构造),其context字段记录了hookName: 'encode'、source: 'extension'、interceptorName: 'anonymous',cause指向原始Error: Something went wrong.。这也解释了输出信息中“use named functions to improve this error message”的提示——给 anyware 拦截器命名可以提升排错体验。
类型层面:HandleOutput 如何推导返回类型
handleOutput的运行时分支在类型系统中有对应的映射(handle.ts)。关键的ConfigGetOutputError会先判断信封是否启用:
export type ConfigGetOutputError< $OutputConfig extends Normalized, $ErrorCategory extends ErrorCategory, > = $OutputConfig['envelope']['enabled'] extends true ? ConfigGetOutputEnvelopeErrorChannel<$OutputConfig, $ErrorCategory> : ConfigResolveOutputErrorChannel<$OutputConfig, $OutputConfig['errors'][$ErrorCategory]>而ConfigGetOutputEnvelopeErrorChannel在“该类别嵌入信封为true”时返回false(即不会作为错误返回),否则回落解析真实通道:
type ConfigGetOutputEnvelopeErrorChannel< $OutputConfig extends Normalized, $ErrorCategory extends ErrorCategory, > = $OutputConfig['envelope']['errors'][$ErrorCategory] extends true ? false : ConfigResolveOutputErrorChannel<$OutputConfig, $OutputConfig['errors'][$ErrorCategory]>配合IfConfiguredGetOutputErrorReturns(handle.ts),当类别通道解析为'return'时返回类型会并入对应错误类型。因此本示例中envelope.errors.other: false意味着 TS 推导的返回类型不会包含“返回错误”的分支——调用方可以放心地按成功数据或异常处理。
进一步组合:return 通道与信封共存
除了throw,Graffle 还支持把错误放进返回值而非抛出。将默认错误通道改为'return'(不启用信封)的示例见 website/content/examples/20_output/return-error.md 与 website/content/examples/20_output/return-error-execution.md:
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 })此时pokemons的类型会推导为“数据 | ContextualError”的联合(示例中通过type _pokemons = typeof pokemons上的// ^?悬浮提示可见),调用方必须显式判别错误分支。这与本示例“信封 + 抛出”形成两种互补的错误处理风格:
- 信封 +
errors: { execution: false, other: false }:错误抛出(throw),异常路径清晰; - 信封 +
errors: { execution: true, other: true }:错误嵌入信封errors数组,数据路径统一; - 关闭信封 +
errorChannel: 'return':错误作为返回值返回,类型系统强制处理。
小结
Graffle 的output配置用“错误类别(execution/other)× 输出通道(throw/return/ 信封嵌入)”的二维模型,为错误处理提供了细粒度的控制:
envelope.errors.execution与envelope.errors.other控制对应错误是否嵌入信封;- 顶层
errors.execution / errors.other与defaults.errorChannel决定未嵌入信封时的最终通道(默认throw); - 运行时由 handleOutput 执行分支决策,类型层面由
HandleOutput系列类型同步推导返回类型,保证“配置即类型”。
当你在使用 envelope 输出模式却希望错误“藏不住”时,只需把对应类别开关设为false,就能得到本示例中直接抛出ContextualError的行为——既保留信封对成功数据{ data }的结构化包装,又让异常不被吞没。
- 后端
【免费下载链接】graffle
Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.
相关推荐
Graffle 配置指南:在 Envelope 输出模式下抛出错误(output.envelope.errors 详解)
Graffle 配置指南:在 Envelope 输出模式下抛出错误(output.envelope.errors 详解) 本文讲解 Graffle GraphQ
后端Graffle 信封(Envelope)模式下强制抛出错误的配置指南:output.envelope.errors 详解
Graffle 信封(Envelope)模式下强制抛出错误的配置指南:output.envelope.errors 详解 导读 本文围绕 Graffle 客户端
后端Graffle 输出配置实战:Envelope 信封模式全解析
Graffle 输出配置实战:Envelope 信封模式全解析 导读 本篇文章聚焦 Graffle(一个极简、可扩展、类型安全的 JavaScript Grap
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考