AbortController已经普及多年,但代码审查里最常见的错误依然和它有关。不是那种"直接报错"的错,是"平时没问题,上线就翻车"的错:幽灵请求覆盖最新状态、超时和用户取消分不清、AbortError被当成致命错误打印、监听器泄漏永远不触发。这些在演示环境都不会暴露,只会在网络抖动加上用户狂点时出现。
以下是2026年API表面下真正经得起生产考验的模式。
取消fetch,以及为什么AbortError不是错误
mechanics很简单,错的是catch里的处理。最常见的bug:一个通吃的catch把所有rejection都送进错误UI。当你中断一个进行中的fetch,promise会reject。如果不特殊处理这个rejection,取消请求就会给"用户主动执行的操作"弹一个错误toast。Abort是正常的控制流结果,不是异常条件。
两个容易搞错的细节:controller.abort()不带参数时,fetch reject的是一个name为"AbortError"的DOMException,上面代码处理的就是这种情况;controller.abort(reason)会用那个reason来reject fetch,如果你传abort(new Error('user navigated')),err.name === 'AbortError'就匹配不上,你会把它重新throw出去。如果用自定义reason,要判断controller.signal.aborted或检查signal.reason,不要匹配name。
controller是一次性的。abort()一旦调用,signal就永久中断("sticky"状态)——把这个signal传给新的fetch会立即中断它。一个操作,一个全新的AbortController。
用AbortSignal.timeout()代替setTimeout套路
手写版本到处都是,而且泄漏:
const controller = new AbortController();
const t = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch(url, { signal: controller.signal });
// BUG: 如果fetch 200ms就resolve,这个timer 4.8秒后还是会触发
} finally {
clearTimeout(t); // 容易忘;没有它timer就一直占着引用
用内置的:
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
AbortSignal.timeout(ms)返回一个ms后自动中断的signal。没有timer句柄要清理,没有东西会忘。两个值得知道的属性:它用name为"TimeoutError"的DOMException中断,不是"AbortError"。这是特性:你可以在同一个catch里终于区分"服务器太慢"和"用户点了取消"。时钟算的是活跃时间,不是挂钟时间,页面后台时会暂停。
热门跟贴