如何避免AbortError: signal is aborted without reason错误
这个错误的核心原因是:当你尝试中止一个已经完成的请求对应的AbortController时,浏览器会抛出AbortError——因为此时信号关联的请求流程已经全部结束,abort()操作已经没有意义了。你代码里的问题在于,请求完成后没有及时清理控制器,导致cancel()方法会遍历到这些已完成请求的控制器,调用abort()时触发错误。
下面给你几个可行的解决方案,按推荐程度排序:
方案1:请求完成后自动清理控制器(最推荐)
在send方法中,当请求完全完成(包括await response.text()执行完之后),把对应的控制器从this.controllers中移除。这样后续调用cancel()时,只会处理那些真正还在进行中的请求控制器。
修改后的send方法:
async send(url) { const controller = new AbortController(); const signal = controller.signal; // 假设requestId是当前请求的唯一标识,你原代码中未定义需自行补充 if (options == null) { options = {} } if (options.signal == null) { options.signal = signal }; this.controllers.set(requestId, controller); try { var response = await fetch(url, options); var text = await response.text(); return response; } finally { // 不管请求成功还是失败,只要流程结束就移除控制器 this.controllers.delete(requestId); } }
用finally块可以确保无论请求成功还是抛出错误,都会清理控制器,既避免后续误操作,也能防止内存泄漏。
方案2:跟踪请求完成状态,仅中止未完成请求
如果因为某些原因你需要保留已完成请求的控制器,可以新增一个Map来记录已完成的请求ID,在cancel()时同时检查请求是否完成。
修改后的MyClass代码片段:
class MyClass { constructor() { this.controllers = new Map(); this.completedRequests = new Map(); // 新增:记录已完成的请求ID } async send(url) { // ... 原有初始化代码 ... try { var response = await fetch(url, options); var text = await response.text(); this.completedRequests.set(requestId, true); // 标记请求已完成 return response; } catch (e) { // 请求失败也标记为完成(可根据业务需求调整) this.completedRequests.set(requestId, true); throw e; } } cancel() { if (this.controllers) { this.controllers.forEach((value, key, map) => { // 只中止「未被中止且未完成」的请求 if (!value.signal.aborted && !this.completedRequests.has(key)) { value.abort(); } }); // 可选:清理已完成的请求记录 this.completedRequests.clear(); } } // ... 其他方法保持不变 ... }
方案3:捕获并忽略预期内的AbortError
如果上面的方案都不适合,你可以直接在调用abort()时捕获这个错误——因为它属于预期内的情况(请求已经完成,中止操作无意义),可以安全忽略。
修改后的cancel方法:
cancel() { if (this.controllers) { this.controllers.forEach((value, key, map) => { if (value.signal.aborted == false) { try { value.abort(); } catch (e) { // 只忽略请求完成后触发的AbortError if (!(e instanceof DOMException && e.name === 'AbortError')) { throw e; // 其他类型的错误正常抛出 } } } }); } }
这个方案是兜底的防御式处理,即使有遗漏的已完成请求控制器,也不会导致代码报错中断。
补充说明
你在MDN看到的注释When abort() is called, the fetch() promise rejects with an AbortError,描述的是请求还在进行中时调用abort()的正常行为——此时fetch的Promise会被中止并抛出错误。而你遇到的错误是请求已经完成时调用abort()的场景,属于额外的错误情况,MDN的注释没有覆盖到这一点。
备注:内容来源于stack exchange,提问作者1.21 gigawatts

