React中Apollo连续调用被取消问题及Client实例化优化咨询
问题
在React代码中编写了如下Hook及点击事件函数:
const [getDataFromGraphql] = useGetDataFromGraphql(); const getData = async (...) => { const res = await getDataFromGraphql({ variables: { ... } }); ... }
其中useGetDataFromGraphql是封装useLazyQuery的自定义Hook:
export default function useGetDataFromGraphql() { return useLazyQuery<..,..>( query, { client: aPrivateClient, } ); }
aPrivateClient是一个ApolloClient实例:
export default new ApolloClient({ link: from([authLink, authExpiryLink, httpLink]), cache, defaultOptions, });
遇到的异常:短时间内多次点击不同按钮(传入不同变量调用同一个getData函数)时,除最后一次调用外,其余所有调用都会被取消,最后一次的结果会覆盖之前的所有结果,尽管每次点击传入的变量不同。
曾尝试对Apollo Client进行memoize但未解决问题,且应用连接了多个客户端,需在Hook中指定具体客户端,单客户端的文档示例不适用。
需求:如何重构配置避免连续网络调用被取消?若客户端实例化是根因,如何确保ApolloClient不会被重新实例化?
解决方案
1. 为每个请求添加唯一上下文标识,避免请求被自动取消
Apollo的useLazyQuery默认会取消同一查询文档+同一客户端实例下的未完成请求,即使变量不同。可以通过为每个请求添加唯一的context标识符,让Apollo将它们识别为独立请求:
const getData = async (...) => { const res = await getDataFromGraphql({ variables: { ... }, // 生成唯一请求ID,确保每个请求上下文独立 context: { requestId: crypto.randomUUID() } }); ... }
也可结合业务需求修改fetchPolicy(比如network-only),进一步明确请求的独立性:
const getData = async (...) => { const res = await getDataFromGraphql({ variables: { ... }, fetchPolicy: 'network-only', context: { requestId: crypto.randomUUID() } }); ... }
2. 确保ApolloClient实例为单例,避免重复创建
尽管当前代码导出了单例aPrivateClient,仍需检查所有导入该客户端的位置,确保没有在组件、Hook内部重复执行new ApolloClient()创建新实例。
若需根据不同场景动态创建客户端,可通过memoize工具缓存实例:
import memoize from 'lodash.memoize'; // 缓存客户端实例,确保同一配置只创建一次 const createPrivateClient = memoize(() => { return new ApolloClient({ link: from([authLink, authExpiryLink, httpLink]), cache, defaultOptions, }); }); // 使用时直接调用,始终返回单例 const aPrivateClient = createPrivateClient();
3. 改造自定义Hook,自动处理请求独立性
修改useGetDataFromGraphql,封装执行逻辑时自动为每个请求添加唯一标识,避免调用方重复编写代码:
export default function useGetDataFromGraphql() { const [executeQuery] = useLazyQuery<..,..>( query, { client: aPrivateClient, } ); // 封装执行函数,自动注入唯一请求ID const executeWithUniqueContext = async (options) => { return executeQuery({ ...options, context: { ...options.context, requestId: crypto.randomUUID() } }); }; return [executeWithUniqueContext]; }
4. 排查自定义Link的拦截逻辑
检查authLink、authExpiryLink等自定义Link,确认是否存在主动取消所有未完成请求的逻辑(比如token刷新时批量取消请求)。若有,需修改逻辑,仅取消与当前操作相关的请求,而非全部请求。
内容的提问来源于stack exchange,提问作者Peter Kim

