如何在Apollo Server中实现单订阅查询FilterSet,避免重复调用第三方API?
解决Apollo Server中DataLoader重复调用第三方API的问题
你的DataLoader实现存在两个核心问题,导致无法正确批处理请求,进而重复调用第三方API:
- 返回结果与请求keys顺序不匹配:DataLoader要求batch函数返回的数组必须和传入的
incIDs顺序完全对应,每个位置的元素对应对应下标的key。你当前代码里过滤后的foundSets是API返回的原始顺序,和请求顺序不一致,这会让DataLoader无法正确关联key和结果,最终触发重复加载。 - 未处理缺失的FilterSet:如果某个请求的FilterSet不存在,返回数组里没有对应位置的元素,DataLoader会认为该key未被加载,后续会重新发起请求。
修正后的代码实现
1. 修复IPFilterSetDataSource的DataLoader逻辑
import axios from 'axios'; import DataLoader from 'dataloader'; const TemplateHeaders = { "Content-Type": "application/json", "Accept": "application/json" }; export class IPFilterSetDataSource { apiKey; batchIPFilterSets; constructor(apiKey) { this.apiKey = apiKey; this.batchIPFilterSets = new DataLoader(async (incIDs) => { // 先按订阅ID分组,避免跨订阅的请求混在一起处理 const subscriptionGroups = incIDs.reduce((groups, fullId) => { const [subscriptionId, filterId] = fullId.split("_"); if (!groups[subscriptionId]) { groups[subscriptionId] = []; } groups[subscriptionId].push({ fullId, filterId }); return groups; }, {}); // 存储每个请求key对应的结果 const resultMap = {}; // 对每个订阅单独发起一次API请求 await Promise.all(Object.entries(subscriptionGroups).map(async ([subscriptionId, requests]) => { const apiOptions = { headers: { ...TemplateHeaders, "Authorization": `Bearer ${this.apiKey}` } }; const ipFilterSets = (await axios.get(`https://example.api.com/v1/subscriptions/${subscriptionId}/ipfiltersets`, apiOptions)).data; // 把FilterSet用code做key存到Map里,方便快速查找 const filterSetMap = ipFilterSets.reduce((map, set) => { map[set.code] = set; return map; }, {}); // 为当前订阅下的每个请求匹配结果,找不到就返回null requests.forEach(({ fullId, filterId }) => { resultMap[fullId] = filterSetMap[filterId] || null; }); })); // 严格按照原始请求的keys顺序返回结果 return incIDs.map(fullId => resultMap[fullId]); }); } async getIpFilterSetForId(subscriptionId, id) { return this.batchIPFilterSets.load(`${subscriptionId}_${id}`); } }
2. Resolver代码(可选优化)
Resolver可以保持原样,不过用Promise.all显式处理Promise数组会更清晰,效果和直接返回map结果一致:
Environment: { ipFilteringSets: async (parent, __, { subscriptionCode, dataSources }) => { return Promise.all( parent.ipFilteringSets.map(s => dataSources.ipFilterSet.getIpFilterSetForId(subscriptionCode, s)) ); } }
关键修改说明
- 按订阅分组处理:同一批次里的请求会按订阅ID分组,每个订阅只发起一次API调用,彻底避免重复请求。
- 结果顺序严格匹配:通过Map快速查找后,按照原始请求的keys顺序返回结果,确保DataLoader能正确关联每个key和对应的结果。
- 处理缺失的FilterSet:对于不存在的FilterSet返回
null,避免DataLoader因结果缺失而重复发起请求。
这样修改后,同一请求中同一订阅的所有FilterSet请求会合并成一次第三方API调用,解决你遇到的重复调用问题。
内容的提问来源于stack exchange,提问作者Jamie Bartlett
相关产品推荐
相关产品推荐

