先记住这个答案
创建AbortController实例,得到signal后放入fetch的配置对象。然后设setTimeout,到指定时间就调用controller.abort()。abort会触发abort事件,fetch监听后取消网络请求并让Promise变成rejected,错误名称为AbortError。务必在请求成功或失败的finally中清除定时器,避免对已完成请求重复abort造成不必要的操作。
- fetch无原生timeout,必须自实现
- AbortController+setTimeout可中断请求
- abort后Promise以AbortError被拒绝
机制:signal如何触发中止
fetch的请求配置支持signal选项,传入AbortSignal后,fetch内部会监听该signal的abort事件。一旦abort被触发,fetch就立即中止当前请求,并让Promise进入rejected状态,错误对象是名为AbortError的DOMException。因此,实现超时的思路就是让定时器去调用controller.abort()。
定时器必须在请求结束后清理。如果请求在时限内完成,不清除定时器会让它稍后触发abort,虽然对已完成的fetch没有实际作用,但浪费回调;如果超时先触发,则定时器已执行,无需再清。所以推荐在请求的then或catch中用clearTimeout,最好放进finally以保证无论哪种情况都执行清理。
场景:30秒超时中断上传
文件上传接口在弱网下可能长时间无响应。要求超过30秒就提示用户并放弃请求。实现上先创建AbortController,将signal传给fetch,再setTimeout设30秒。到点调用abort(),在catch中检查抛出的error.name是否等于'AbortError',若是则显示'上传超时',否则按网络错误处理。
要注意,即使调用abort,服务器端可能仍在接收文件,因为连接只被浏览器侧切断。所以接口应支持断点重传或幂等性,避免产生不完整数据。另外,请求若在30秒内成功,必须在成功处理中clearTimeout,阻止超时abort干扰后续逻辑。
失效条件与处理
AbortController的abort不能取消服务器端的操作,只能中断浏览器到服务器的连接。如果请求已经到达服务器,那么服务端仍会继续执行,甚至可能返回响应,但浏览器已放弃。还有,如果主线程被长期阻塞,setTimeout的回调可能不准时,造成超时时间延后。
处理措施:在catch中只对AbortError做超时提示,其他错误走常规分支,以精确区分;同时所有请求分支(成功、失败)都要清除定时器。若需要更严格的超时,可把定时器放在独立worker,但一般场景够用。另外,AbortController也可并发取消多个请求,但此场景只需一个。
容易答错的地方
- fetch自带timeout参数?
- 误区:以为fetch支持timeout配置。规范未提供,需要手动用AbortController实现。即使某些库扩展了选项,也不原生。
- abort会撤销服务器处理?
- 错误理解:认为调用abort会阻止服务器端继续工作。实际上仅中断浏览器侧,服务器可能已处理请求。需要服务端支持取消或幂等。
面试官还会怎么问?
fetch超时和XHR的timeout有什么本质区别?
XHR有内置timeout属性,超时触发ontimeout事件;fetch需自造,abort后Promise以AbortError被拒绝,要在catch中靠name判断。
如何准确识别一个错误是AbortError?
在catch中检查error.name === 'AbortError'。也可用error instanceof DOMException判断,但name是最稳的。
同一个AbortController可以给多个fetch用吗?
可以,signal可传给多个fetch,调用abort会同时中断它们。适用于批量请求取消,但超时场景只绑一个更清晰。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。