给前端请求装上刹车:AbortController 的取消、超时与竞态处理实战
在做搜索框联想、表格分页、文件上传时,几乎都会遇到同一个尴尬:请求发出去了就收不回来。用户已经切换到别的页面,接口还在跑;搜索框每敲一个字就发一次请求,先发出的慢接口反而后返回,把最新结果盖掉;上传到一半想取消,只能刷新页面。问题的根子不在接口本身,而在请求缺少一个可控的“刹车”。AbortController 就是浏览器原生提供的这套刹车机制。
一、AbortController 是什么
AbortController 是浏览器内置的取消信号控制器。它本身不发请求,只产生一个 signal 对象。把 signal 传给 fetch,之后随时调用 controller.abort(),对应的请求就会被中断,fetch 的 Promise 以 AbortError 结束。一个控制器对应一次取消动作,signal 只能用一次,取消后不能复用,想再发请求就要新建控制器。
二、基础用法:取消一次请求
以加载列表为例:进入页面时创建控制器并发起请求,组件卸载或用户点击取消时调用 abort()。被取消的请求会在 catch 里抛出名字为 AbortError 的异常,判断 err.name === 'AbortError' 后直接忽略即可,千万不要把它当成真正的网络故障弹给用户。这是使用 AbortController 时最容易出错的地方。
三、给请求加上超时
fetch 本身没有超时参数,过去只能自己写 Promise.race。现在可以借助 AbortSignal.timeout(),一行代码限定最长等待时间,超过时间请求自动取消。超时时长要和业务耐心匹配:列表查询 5 秒合理,文件上传则要放得更宽,或者干脆不设超时,只提供手动取消。
四、解决搜索联想里的竞态问题
搜索框是最经典的竞态场景。正确做法是:每次输入前先取消上一次请求,再创建新控制器发起本次请求。这样同一时刻在途的只有一个请求,后返回的旧结果再也不会覆盖新结果。配合 300 毫秒左右的防抖,接口调用量和页面错乱感会同时下降。顺序一定是先 abort 再新建,否则取消的可能是刚发出去的新请求。
五、一个信号管多个请求
AbortSignal.any() 可以把多个信号合并成一个:用户取消、超时、页面关闭,任意一个条件触发,关联的请求全部中断。批量下载、表单里多个并行校验都适合这种写法。清理同样重要:页面卸载、弹窗关闭时调用 abort(),既能释放连接,也能避免回调里操作已经不存在的界面元素,减少内存泄漏和报错。
六、容易踩的几个坑
第一,signal 是一次性的,复用会直接抛错。第二,取消是静默的,不会自动重试,确有必要时要由业务层决定是否重新发起。第三,axios 使用自己的取消机制或 signal 配置,写法不同,原理相通。第四,并不是所有后端都能即时感知到取消,服务端任务可能仍在跑,关键的写操作不要依赖前端取消来保证一致性。
总结一下:请求不是发出去就不管的“一次性用品”。给每一次 fetch 配上明确的取消条件——页面卸载取消、超时取消、新输入取消旧请求,页面会更干净,流量更节省,用户看到的永远是最新、最正确的结果。下次写代码时,不妨先问自己一句:这个请求,取消的时机想好了吗?
- 点赞
- 收藏
- 关注作者
评论(0)