AbortController提前终止问题:切换筛选器时终止旧请求方案排查
筛选器变更时终止请求的问题与解决方案
方案一的问题分析
const API_URL = "https://www.example.com" const controller = new AbortController(); const signal = controller.signal; export const fetchData = async (filters) => { // this console.log is fired only once console.log("Below I thought it would abort a request if ongoing, and ignore otherwise"); controller.abort(); const response = await fetch(`${API_URL}/products?${filters}`, { method: "GET", signal }); return await response.json(); }
问题出在全局唯一的AbortController:首次调用fetchData时,你先执行了controller.abort(),此时关联的signal已经处于终止状态,后续的fetch请求一发起就会被直接终止。而且这个控制器是全局固定的,后续所有调用都会复用这个已失效的signal,根本无法发起有效请求。
方案二的问题分析
let controller; let signal; export const fetchData = async (filters) => { if (controller) { console.log("aborting") controller.abort(); controller = null; fetchData(filters); // won't work until I invoke this function here recursively // and I expected something like // controller = new AbortController(); // signal = controller.signal; // ... then the rest of the function would work } else { controller = new AbortController(); signal = controller.signal; } const response = await fetch(`${API_URL}/products?${filters}`, { method: "GET", signal }); console.log("fetch fulfilled") return await response.json(); }
为什么需要递归调用?
当你执行controller.abort()后,后续的fetch会因为signal终止抛出AbortError,如果不捕获这个错误,函数会直接中断,根本走不到重新创建控制器的逻辑。递归调用其实是重新触发一次请求,但这会带来额外的函数调用栈开销。为什么"fetch fulfilled"打印两次?
递归调用会触发第二次fetchData,如果第一次请求在被终止前已经完成(比如网络延迟导致终止指令没及时生效),就会打印一次;第二次请求成功后又会打印一次。另外递归是异步执行的,第一次函数的fetch可能在终止前已经返回结果,也会导致重复打印。
正确实现方案
核心思路:每次发起新请求时,先终止上一次未完成的请求(如果存在),创建新的AbortController用于当前请求,同时捕获AbortError避免程序崩溃。
const API_URL = "https://www.example.com"; let currentController = null; export const fetchData = async (filters) => { // 终止上一次未完成的请求 if (currentController) { currentController.abort(); } // 创建新的控制器用于当前请求 const controller = new AbortController(); currentController = controller; try { const response = await fetch(`${API_URL}/products?${filters}`, { method: "GET", signal: controller.signal, }); // 请求成功后清空当前控制器,避免后续误操作 currentController = null; return await response.json(); } catch (error) { // 仅忽略终止请求的错误,其他错误正常抛出 if (error.name !== "AbortError") { throw error; } } };
关键说明
- 用
currentController跟踪当前活跃的请求控制器,确保每次新请求都能终止上一个未完成的请求。 - 每次请求创建独立的控制器,保证当前请求的
signal处于有效状态。 - 捕获
AbortError,避免请求被终止时抛出错误影响程序正常运行。 - 请求成功后清空
currentController,防止后续无意义的终止操作。
内容的提问来源于stack exchange,提问作者deaponn
相关产品推荐
相关产品推荐

