React中发送GraphQL Mutation时setTimeout相关问题及替代方案
GraphQL useMutation 同步调用被取消问题分析与解决
问题背景
需要将单个表单的值拆分为两部分,通过GraphQL的calc mutation发送到同一接口。使用React的useMutation Hook实现时发现:通过setTimeout延迟调用第二个mutation,两个请求均能成功;但同步调用时,第二个请求会处于取消状态且无响应。
相关代码
GraphQL Mutation
mutation calc($applicationRecipientsFilter: ApplicationRecipientsFilterInput) { create(applicationRecipientsFilter: $applicationRecipientsFilter) { id city { id name } applicationType recipients } }
Mutation Hook封装
const { mutate } = useMutation(calc); const addNewNotification = useCallback( async (args) => { try { const { error } = await mutate(args); if (error) { console.log(error) return; } } catch (e) { console.log(e) } }, [mutate], );
组件调用逻辑
// 延迟调用(执行成功) const handleAddNotification = useCallback(() => { const { city, email, email2 } = values; // 来自useFormik的表单值 const currentCity = citiesOption.find(({ value }) => value === city); const createArguments = (applicationType, recipients) => ({ applicationRecipients: { city: { name: currentCity?.label, id: currentCity?.value, }, applicationType, recipients, }, }); addNewNotification(createArguments(creditTypeOption[0]?.value, email)); setTimeout(() => addNewNotification(createArguments(creditTypeOption[1]?.value, email2)), 1500); }, [addNewNotification, creditTypeOption, values, citiesOption]); // 同步调用(请求被取消) // addNewNotification(createArguments(creditTypeOption[0]?.value, email)); // addNewNotification(createArguments(creditTypeOption[1]?.value, email2));
请求状态截图:
- 取消状态:

- 成功状态:

问题原因
Apollo Client的useMutation钩子返回的mutate函数,默认会自动取消之前尚未完成的同mutation请求。这是Apollo内置的竞态处理机制,用于避免旧请求结果覆盖新请求,但在需要并行发送同mutation的场景下,该机制会导致先发起的请求被后一个请求直接取消。
使用setTimeout延迟调用时,第一个请求已完成,第二个请求不会触发取消逻辑,因此能正常执行。
不使用setTimeout的解决方案
方案1:调用mutate时禁用取消未完成请求
在调用mutate时通过context设置cancelRefetch: false,允许同mutation的多个请求并行执行,代码改动最小:
const addNewNotification = useCallback( async (args) => { try { const { error } = await mutate(args, { context: { cancelRefetch: false // 禁用同mutation请求的自动取消 } }); if (error) { console.log(error) return; } } catch (e) { console.log(e) } }, [mutate], );
之后可直接同步调用,或用Promise.all并行执行:
const handleAddNotification = useCallback(async () => { // 省略createArguments等逻辑 await Promise.all([ addNewNotification(createArguments(creditTypeOption[0]?.value, email)), addNewNotification(createArguments(creditTypeOption[1]?.value, email2)) ]); }, [addNewNotification, creditTypeOption, values, citiesOption]);
方案2:创建两个独立的useMutation实例
如果需要频繁并行发送该mutation,可直接创建两个独立的useMutation钩子,它们的请求不会互相干扰:
const { mutate: mutate1 } = useMutation(calc); const { mutate: mutate2 } = useMutation(calc); const addNewNotification1 = useCallback( async (args) => { try { const { error } = await mutate1(args); if (error) console.log(error); } catch (e) { console.log(e) } }, [mutate1], ); const addNewNotification2 = useCallback( async (args) => { try { const { error } = await mutate2(args); if (error) console.log(error); } catch (e) { console.log(e) } }, [mutate2], ); // 组件调用 const handleAddNotification = useCallback(() => { // 省略createArguments等逻辑 addNewNotification1(createArguments(creditTypeOption[0]?.value, email)); addNewNotification2(createArguments(creditTypeOption[1]?.value, email2)); }, [addNewNotification1, addNewNotification2, creditTypeOption, values, citiesOption]);
方案3:修改mutation的fetchPolicy
结合fetchPolicy: 'no-cache'使用(适合不需要缓存的场景),同时配合cancelRefetch: false效果更好:
const { mutate } = useMutation(calc, { fetchPolicy: 'no-cache' });
总结
最推荐方案1,通过cancelRefetch: false灵活控制请求并行逻辑,无需额外创建多个mutation实例,代码侵入性最低。
内容的提问来源于stack exchange,提问作者jeje
相关产品推荐
相关产品推荐

