React中调用AbortController.abort()后fetch请求仍返回结果的问题
问题:React中AbortController取消fetch请求无效的排查与解决
我在React中实现一个功能:当旧的fetch请求未完成时,发起新请求则取消旧请求并调用新请求。之前在useEffect钩子中实现过该功能且运行正常,但本次用普通函数实现时,尽管调用了signal的abort()方法,fetch请求的结果仍会显示。
原代码
let submitDebouncer; let submitAborter = null; function sleep(ms){ return new Promise(resolve => setTimeout(resolve, ms)) } async function fetcher(url){ if(submitAborter){ console.log(submitAborter); submitAborter.abort(); console.log(submitAborter); } submitAborter = new AbortController(); const runSignal = submitAborter.signal; try{ const response = await fetch(url, {runSignal}); const data = await response.json(); submitAborter = null; setDisplayData(data); console.log(data); } catch(e){ console.log(e.name) } } function handleSubmit(){ if(submitDebouncer){ clearTimeout(submitDebouncer); } setLoading(true) submitDebouncer = setTimeout(() => { const url = `http://api.openweathermap.org/geo/1.0/direct?q=${city}&limit=1&appid=${API_Key}`; fetcher(url) sleep(50) fetcher(url) }, 200) }
输出显示aborted状态已设为true,但仍收到请求结果,预期旧请求会被中止,不会显示冗余结果。
问题排查与修正方案
核心问题点
- fetch参数传递错误:你用了
{runSignal}的对象简写,但fetch要求的参数键是signal,这导致实际没有把AbortSignal传给fetch,旧请求自然不会被中止。 - Controller覆盖时机错误:调用
abort()后立刻把submitAborter替换为新的AbortController,导致第一个请求的signal丢失,后续abort操作无法影响它。 - 异步操作未正确等待:
sleep(50)后直接调用fetcher(url),sleep不会等待,两个请求几乎同时发起,逻辑不符合预期。 - 中止后仍可能执行状态更新:即使请求被中止,若响应已经返回,仍会执行
setDisplayData,没有额外检查signal状态。
修正后的代码
let submitDebouncer; let submitAborter = null; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)) } async function fetcher(url) { // 中止之前的请求 if (submitAborter) { submitAborter.abort(); } // 创建新控制器并保存,避免过早覆盖旧控制器 const currentController = new AbortController(); submitAborter = currentController; const signal = currentController.signal; try { // 正确传递signal参数给fetch const response = await fetch(url, { signal }); // 检查是否已中止,避免处理后续逻辑 if (signal.aborted) return; const data = await response.json(); // 解析JSON后再次检查,防止过程中请求被中止 if (signal.aborted) return; setDisplayData(data); console.log(data); } catch (e) { // 忽略中止错误,只处理其他请求异常 if (e.name !== 'AbortError') { console.error('请求出错:', e); } } finally { // 仅当当前控制器仍是全局保存的实例时才清空,避免影响后续请求 if (submitAborter === currentController) { submitAborter = null; } } } function handleSubmit() { if (submitDebouncer) { clearTimeout(submitDebouncer); } setLoading(true); submitDebouncer = setTimeout(() => { const url = `http://api.openweathermap.org/geo/1.0/direct?q=${city}&limit=1&appid=${API_Key}`; fetcher(url); // 正确等待sleep完成后再发起新请求 sleep(50).then(() => { fetcher(url); }); }, 200); }
关键修正说明
- 确保fetch接收正确的
signal参数,让请求能响应中止信号。 - 先保存新控制器再发起请求,避免旧请求的signal被提前覆盖。
- 在请求响应和JSON解析后都检查signal状态,防止中止后仍执行状态更新。
- 用
then等待sleep完成,确保两次请求有50ms间隔,符合测试逻辑。 - 在finally中判断后再清空控制器,避免误删后续请求的控制器实例。
内容的提问来源于stack exchange,提问作者Rohan Garg
相关产品推荐
相关产品推荐

