GraphQL中如何在UserType的anotherField解析器中调用subscriptions解析器?
我现在有一个用户订阅事件的GraphQL场景,代码定义如下:
const EventType = new GraphQLObjectType({ name:'Event', fields:() => ({ id: {type:GraphQLID} }) }); const UserType = new GraphQLObjectType({ name:'User', fields:() => ({ id: {type:GraphQLString}, _subscriptionIds: { type: new GraphQLList(GraphQLID) }, subscriptions: { type: new GraphQLList(EventType), async resolve(parent, args) { return Event.find( {_id: { $in: parent._subscriptionIds}}) } }, anotherField: { type: new AnotherType, async resolve(parent, args) { console.log(parent.subscriptions) // parent.subscriptions为undefined,我需要解析它 } } }) });
简而言之,我需要在anotherField的解析器中访问已经解析完成的parent.subscriptions(Event类型数组),也就是“强制调用”subscriptions的解析器并获取它的结果。请问这是否可行?该如何实现?谢谢!
这当然是可行的,但要注意GraphQL字段的解析默认是并行执行的,所以anotherField和subscriptions的解析器会同时启动,这就是为什么你直接访问parent.subscriptions会得到undefined——因为它还没被解析完成。
这里有两种常用的解决方案:
1. 提取公共解析逻辑,在anotherField中手动调用
把subscriptions的解析逻辑抽成一个独立的函数,这样两个字段都可以复用它:
// 提取公共逻辑 const getSubscriptions = async (parent) => { return Event.find({ _id: { $in: parent._subscriptionIds } }); }; const UserType = new GraphQLObjectType({ name:'User', fields:() => ({ id: {type:GraphQLString}, _subscriptionIds: { type: new GraphQLList(GraphQLID) }, subscriptions: { type: new GraphQLList(EventType), async resolve(parent, args) { return getSubscriptions(parent); } }, anotherField: { type: new AnotherType, async resolve(parent, args) { const subscriptions = await getSubscriptions(parent); console.log(subscriptions); // 现在能拿到正确的结果了 // 这里继续你的anotherField逻辑 } } }) });
这种方法简单直接,但要注意如果两个字段都被请求的话,会触发两次数据库查询。如果你想避免重复查询,可以用下面的方法。
2. 使用DataLoader缓存查询结果
DataLoader是专门用来批量处理和缓存数据库查询的工具,能有效避免重复请求。
首先安装DataLoader:
npm install dataloader
然后创建一个Event的DataLoader:
const DataLoader = require('dataloader'); const eventLoader = new DataLoader(async (ids) => { const events = await Event.find({ _id: { $in: ids } }); // 要保证返回的数组顺序和传入的ids顺序一致 return ids.map(id => events.find(event => event._id.toString() === id)); });
接着修改你的解析器:
const UserType = new GraphQLObjectType({ name:'User', fields:() => ({ id: {type:GraphQLString}, _subscriptionIds: { type: new GraphQLList(GraphQLID) }, subscriptions: { type: new GraphQLList(EventType), async resolve(parent, args) { return Promise.all(parent._subscriptionIds.map(id => eventLoader.load(id))); } }, anotherField: { type: new AnotherType, async resolve(parent, args) { const subscriptions = await Promise.all(parent._subscriptionIds.map(id => eventLoader.load(id))); console.log(subscriptions); // 这里拿到的是缓存后的结果,不会重复查询 // 继续你的逻辑 } } }) });
这样不管是subscriptions还是anotherField请求数据,DataLoader都会自动缓存结果,避免重复查询数据库。
3. 利用GraphQL上下文传递结果(不推荐)
你也可以在subscriptions的解析器中把结果存到上下文里,然后在anotherField中读取,但这种方法会让代码耦合度变高,而且依赖字段的解析顺序(虽然大部分GraphQL实现是按定义顺序执行,但这不是规范保证的,存在不稳定风险),所以不推荐使用。
总结一下,最推荐的是第二种方法(DataLoader),既解决了重复查询的问题,又能保证代码的可维护性;如果场景简单,第一种方法也完全够用。
内容的提问来源于stack exchange,提问作者lolo.io

