GraphQL中如何将子解析器错误传播至父解析器
解决方案
方案1:基于请求字段按需调用API + 错误聚合(最直接可靠)
利用GraphQL解析器的info参数获取客户端实际请求的字段,仅调用对应所需的REST API,同时通过Promise.allSettled捕获所有请求的错误状态,最终统一设置success字段。
这种方式既解决了原解析器冗余调用API的性能问题,又能完全控制success字段的取值,无需修改现有架构:
async function getUser(parent, args, context, info) { // 提取客户端请求的字段集合 const requestedFields = new Set( info.fieldNodes[0].selectionSet.selections.map(sel => sel.name.value) ); const result = { success: true }; const taskResults = []; // 仅为请求的字段发起API调用,捕获错误并标记字段类型 if (requestedFields.has('userInfo')) { taskResults.push( fetchUserInfo(args.userId) .then(data => ({ type: 'userInfo', data })) .catch(err => ({ type: 'userInfo', error: err })) ); } if (requestedFields.has('currency')) { taskResults.push( fetchCurrency(args.userId) .then(data => ({ type: 'currency', data })) .catch(err => ({ type: 'currency', error: err })) ); } // 等待所有任务完成,聚合结果与错误 const settled = await Promise.allSettled(taskResults); settled.forEach(item => { if (item.status === 'fulfilled') { const { type, data } = item.value; result[type] = data; } else { const { type, error } = item.value; result.success = false; // 按架构要求设置字段为null或合法默认值 result[type] = null; // 可选:将错误信息存入上下文或日志 context.logger.error(`Failed to fetch ${type}: ${error.message}`); } }); // 为未请求的字段设置架构允许的默认值(如null) if (!requestedFields.has('userInfo')) result.userInfo = null; if (!requestedFields.has('currency')) result.currency = null; return result; }
方案2:上下文共享错误状态 + 子解析器错误捕获
如果坚持拆分字段为独立解析器,可以通过GraphQL的context对象共享错误状态:
- 父解析器初始化
context.userErrors = [] - 子解析器(
userInfo/currency)捕获API错误,将错误信息推入context.userErrors,同时返回架构允许的默认值(如null) - 父解析器通过GraphQL的异步钩子或字段完成回调(部分GraphQL服务器支持,如Apollo Server的
didResolveField)等待所有子字段解析完成后,检查context.userErrors是否有内容,最终修改success字段
示例(以Apollo Server为例):
// 父解析器 async function getUser(parent, args, context) { context.userErrors = []; // 返回基础结构,子解析器填充字段 return { success: true, userInfo: null, currency: null }; } // userInfo子解析器 async function userInfo(parent, args, context) { try { return await fetchUserInfo(args.userId); } catch (err) { context.userErrors.push('userInfo'); return null; // 符合架构要求 } } // Apollo Server配置中添加生命周期钩子 const server = new ApolloServer({ typeDefs, resolvers, plugins: [ { async didResolveField(ctx) { // 仅在getUser字段的子字段解析完成后检查 if (ctx.parentType.name === 'UserSuccessfulResponse' && ctx.fieldName !== 'success') { const root = ctx.source; // 只要有子字段错误,就把success设为false if (ctx.context.userErrors.length > 0) { root.success = false; } } } } ] });
方案选择建议
- 优先选方案1:无需依赖服务器特定钩子,逻辑独立可控,完全避免冗余API调用,同时精准控制
success字段 - 方案2适合已经拆分好子解析器、不想重构的场景,但需要适配不同GraphQL服务器的生命周期API
内容的提问来源于stack exchange,提问作者sharkbait
相关产品推荐
相关产品推荐

