React中Apollo Client useMutation终止旧请求致新请求无法执行
一、useRef写法无效的原因
useMutation在组件首次渲染时,就把当时的controller.current.signal引用绑定到了生成的mutationFunction内部。后续你把controller.current换成新的AbortController,但mutationFunction用的还是最开始那个已经被abort()的信号。所以你先终止旧信号,再调用mutationFunction时,请求刚发起就会被已终止的信号取消,自然跑不起来。
二、await setController看似生效的原因
首先明确:useState的更新函数根本不返回Promise,所以await setController()是完全错误的写法,没有任何合理依据。它看似能工作只是巧合——await undefined等价于等待一个已resolve的空Promise,会把后续代码推迟到微任务队列执行。在这个延迟的间隙,组件可能刚好完成了setController触发的重新渲染,生成了绑定新信号的新mutationFunction。但这种行为完全不可靠,依赖渲染时机的偶然,属于歪打正着的错误写法。
三、Apollo Client中取消旧请求的正确方法
核心思路是:每次调用mutationFunction时动态传入新的请求信号,而不是在useMutation初始化时就固定绑定死信号。
正确实现代码
const controller = useRef(new AbortController()); // 初始化useMutation时不固定context配置 const [mutationFunction, result] = useMutation(THE_MUTATION); const handleRequest = async () => { // 终止正在进行的旧请求 controller.current.abort(); // 创建新的控制器实例 controller.current = new AbortController(); // 调用mutation时,动态传入带新信号的context await mutationFunction(null, { context: { fetchOptions: { signal: controller.current.signal } } }); }
更健壮的方案(独立管理每个请求的控制器)
如果不想复用控制器,也可以每次请求都创建全新的AbortController,同时用useRef跟踪当前请求的控制器,确保旧请求能被正确终止:
const [mutationFunction, result] = useMutation(THE_MUTATION); // 用ref存储当前活跃的请求控制器 const activeController = useRef(null); const handleRequest = async () => { // 终止上一次未完成的请求 if (activeController.current) { activeController.current.abort(); } // 创建新的控制器 const newController = new AbortController(); activeController.current = newController; try { // 发起新请求,传入新信号 await mutationFunction(null, { context: { fetchOptions: { signal: newController.signal } } }); } finally { // 请求完成后,清空当前控制器引用(避免误终止后续请求) if (activeController.current === newController) { activeController.current = null; } } }
这样每次发起新请求时,都会使用全新的信号实例,旧请求会被立即终止,新请求不受任何影响,是Apollo Client中处理这类场景的标准做法。
内容的提问来源于stack exchange,提问作者Michaila

