Vue组件销毁时为什么之前的AJAX请求没被取消?

Tr° 鑫丹 阅读 52

在做搜索框实时查询功能时,当我快速输入多个字符导致多次发送请求,虽然用了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里又可以正常获取数据。那应该怎样正确在组件销毁时取消未完成的请求呢?

我来解答 赞 8 收藏
二维码
手机扫码查看
2 条解答
端木风珍
你这个写法根本不是「组件销毁时取消请求」,而是「每次请求刚发出去就自己把自己 abort 了」,所以根本没机会拿到数据——你看到 setTimeout 能工作,是因为它把 abort 延后了,请求有时间返回了。

问题出在两个地方:

第一,你每次调用 search() 时,都新建了一个 AbortController,但没管上一次的;如果前一个请求还没完成,它其实还在等响应,但你已经新建了新的 controller,旧的 controller 被垃圾回收了,但它对应的请求不会自动取消(除非你显式调用它的 abort)。

第二,你根本没在组件销毁时做任何取消操作,所以跳转页面时,那些还在 pending 的请求就还在跑,最后返回时组件都销毁了,再执行 then/catch 就会抛出 AbortError,或者更糟——操作一个已卸载的组件导致其他报错。

正确做法是:

1. 每次新请求前,先取消上一次的(用同一个 controller 或保存上一个的 signal)
2. 在组件销毁钩子里,统一取消所有未完成的请求

比如这样写:

export default {
data() {
return {
currentController: null
}
},
methods: {
search() {
// 如果有正在进行的请求,先取消它
if (this.currentController) {
this.currentController.abort()
}
// 新建一个 controller
this.currentController = new AbortController()

fetch(/api/search?term=${this.term}, {
signal: this.currentController.signal
})
.then(res => res.json())
.then(data => {
// 这里可以加个判断,避免组件已销毁还更新数据
if (!this.currentController || this.currentController.signal.aborted) return
// 处理数据
})
.catch(err => {
// AbortError 是正常的取消,别当错误处理
if (err.name === 'AbortError') return
console.error('请求失败', err)
})
}
},
beforeDestroy() {
// 组件销毁时,把最后一个还在跑的也取消掉
if (this.currentController) {
this.currentController.abort()
}
}
}


注意几个细节:

- 用 currentController 一直指向「当前这次搜索」的 controller,不是每次新建完就扔掉
- fetch.catch 里要过滤掉 AbortError,否则它就会在控制台冒出来
- beforeDestroy(Vue2)或 beforeUnmount(Vue3)里统一 abort 一次,防止跳转后还回来数据

另外建议:如果用 axios 的话,直接用它自带的 cancelToken 更省事,但原理一样——在销毁前把所有 pending 的 cancel 掉。
点赞 5
2026-02-27 08:00
Dev · 文鑫
你现在的写法有几个问题,我来一步步帮你理清楚。首先你要明白,AbortController 的作用是用来取消指定的请求,但你在 search 方法里每次调用都会新建一个 AbortController 实例,并且马上调用 abort,这就导致所有请求都会被立刻取消。这显然是不符合预期的。

其次,页面跳转时控制台报 AbortError,是因为组件销毁时没有正确处理那些还没完成的请求。我们得在组件销毁时手动取消这些请求,同时保证正常的请求流程不会被打断。

下面是一个正确的解决方案,分步骤说明:

第一步,在组件的 data 里维护一个 AbortController 的实例,而不是每次调用 search 时都创建新的实例。这样可以确保同一个控制器管理所有的请求。

第二步,在组件销毁时,也就是 Vue 的 beforeDestroy 生命周期钩子里,调用 abort 来取消所有未完成的请求。这是关键点,因为组件销毁时如果不取消请求,浏览器可能还会尝试处理这些请求,导致报错。

第三步,不要在 search 方法里直接调用 abort,而是只负责发起新请求。每次发起新请求前,先检查是否有旧的控制器存在,如果有就取消旧的请求,然后创建一个新的控制器。

下面是修改后的代码:

export default {
data() {
return {
controller: null // 维护一个 AbortController 实例
}
},
methods: {
search() {
// 如果已经有旧的控制器,先取消之前的请求
if (this.controller) {
this.controller.abort()
}
// 创建新的控制器
this.controller = new AbortController()
const signal = this.controller.signal

// 发起新的请求
fetch(/api/search?term=${this.term}, { signal })
.then(response => {
if (!response.ok) throw new Error('Network response was not ok')
return response.json()
})
.then(data => {
console.log('搜索结果:', data)
// 只有成功的请求才会走到这里
})
.catch(error => {
if (error.name === 'AbortError') {
console.log('请求被取消了') // 这是正常现象,不用处理
} else {
console.error('请求出错了:', error)
}
})
}
},
beforeDestroy() {
// 组件销毁时取消所有未完成的请求
if (this.controller) {
this.controller.abort()
console.log('组件销毁,取消所有未完成的请求')
}
}
}


解释一下这段代码的逻辑:每次调用 search 方法时,如果已经有一个控制器存在,我们就先调用 abort 取消之前的请求,然后再创建新的控制器发起新请求。当组件销毁时,我们在 beforeDestroy 钩子里再次调用 abort,确保没有任何遗留的请求还在运行。

另外,关于你提到的把 abort 放在 setTimeout 里的做法,虽然看起来能解决问题,但实际上是一种不优雅的 hack 手段。它并不能真正解决组件销毁时的请求取消问题,反而会让代码变得难以维护。所以还是建议按照上面的方式处理。

最后再提醒一点,AbortError 是正常的现象,表示请求被成功取消了,不需要担心这个错误。你只需要在 catch 里判断错误类型,如果是 AbortError 就忽略它即可。

希望这些内容能帮到你,有问题随时问!
点赞 6
2026-02-14 17:26