Vue组件销毁时为什么之前的AJAX请求没被取消?
在做搜索框实时查询功能时,当我快速输入多个字符导致多次发送请求,虽然用了abortController,但页面跳转时控制台还是报错”AbortError”,之前的请求好像没完全取消。
我的代码是这样写的:
<template>
<input @input="search">
</template>
<script>
export default {
data() {
return {
controller: null
}
},
methods: {
search() {
this.controller = new AbortController()
fetch(api/search?term=${this.term}, { signal: this.controller.signal })
.then(...) // 处理响应
this.controller.abort() // 立即调用取消
}
}
}
</script>
问题是我这样写会导致所有请求都被立刻取消,但如果把abort放在setTimeout里又可以正常获取数据。那应该怎样正确在组件销毁时取消未完成的请求呢?
问题出在两个地方:
第一,你每次调用
search()时,都新建了一个AbortController,但没管上一次的;如果前一个请求还没完成,它其实还在等响应,但你已经新建了新的 controller,旧的 controller 被垃圾回收了,但它对应的请求不会自动取消(除非你显式调用它的 abort)。第二,你根本没在组件销毁时做任何取消操作,所以跳转页面时,那些还在 pending 的请求就还在跑,最后返回时组件都销毁了,再执行 then/catch 就会抛出
AbortError,或者更糟——操作一个已卸载的组件导致其他报错。正确做法是:
1. 每次新请求前,先取消上一次的(用同一个 controller 或保存上一个的 signal)
2. 在组件销毁钩子里,统一取消所有未完成的请求
比如这样写:
注意几个细节:
- 用
currentController一直指向「当前这次搜索」的 controller,不是每次新建完就扔掉-
fetch的.catch里要过滤掉AbortError,否则它就会在控制台冒出来-
beforeDestroy(Vue2)或beforeUnmount(Vue3)里统一 abort 一次,防止跳转后还回来数据另外建议:如果用 axios 的话,直接用它自带的 cancelToken 更省事,但原理一样——在销毁前把所有 pending 的 cancel 掉。
AbortController的作用是用来取消指定的请求,但你在search方法里每次调用都会新建一个AbortController实例,并且马上调用abort,这就导致所有请求都会被立刻取消。这显然是不符合预期的。其次,页面跳转时控制台报
AbortError,是因为组件销毁时没有正确处理那些还没完成的请求。我们得在组件销毁时手动取消这些请求,同时保证正常的请求流程不会被打断。下面是一个正确的解决方案,分步骤说明:
第一步,在组件的
data里维护一个AbortController的实例,而不是每次调用search时都创建新的实例。这样可以确保同一个控制器管理所有的请求。第二步,在组件销毁时,也就是 Vue 的
beforeDestroy生命周期钩子里,调用abort来取消所有未完成的请求。这是关键点,因为组件销毁时如果不取消请求,浏览器可能还会尝试处理这些请求,导致报错。第三步,不要在
search方法里直接调用abort,而是只负责发起新请求。每次发起新请求前,先检查是否有旧的控制器存在,如果有就取消旧的请求,然后创建一个新的控制器。下面是修改后的代码:
解释一下这段代码的逻辑:每次调用
search方法时,如果已经有一个控制器存在,我们就先调用abort取消之前的请求,然后再创建新的控制器发起新请求。当组件销毁时,我们在beforeDestroy钩子里再次调用abort,确保没有任何遗留的请求还在运行。另外,关于你提到的把
abort放在setTimeout里的做法,虽然看起来能解决问题,但实际上是一种不优雅的 hack 手段。它并不能真正解决组件销毁时的请求取消问题,反而会让代码变得难以维护。所以还是建议按照上面的方式处理。最后再提醒一点,
AbortError是正常的现象,表示请求被成功取消了,不需要担心这个错误。你只需要在catch里判断错误类型,如果是AbortError就忽略它即可。希望这些内容能帮到你,有问题随时问!