Vue长轮询请求在页面切换时如何正确关闭?
在Vue项目里用长轮询实时获取订单状态,但切换页面时老是报错“Cannot read properties of undefined (reading ‘then’)”。代码是这样写的:
<template>
<div>订单状态:{{ status }}</div>
</template>
<script>
export default {
data() {
return { status: '' };
},
mounted() {
this.poll();
},
methods: {
async poll() {
try {
const res = await axios.get('/api/poll', { timeout: 30000 });
this.status = res.data.status;
this.poll(); // 递归调用继续轮询
} catch (err) {
setTimeout(() => this.poll(), 5000);
}
}
}
}
</script>
我在beforeDestroy里加过this.poll = null,但控制台还是不断报错。页面切换时应该怎样彻底终止这个递归请求呢?
更严重的是,你在 catch 里还用了 setTimeout 继续轮询,这会导致即使出错也会无限重试,页面切走之后这些请求和定时器全都在后台跑着,内存泄漏都算轻的。
正确的做法分三步:
第一步,加一个标志位来控制是否继续轮询。这样可以在组件销毁时主动中断递归
第二步,在 beforeUnmount 或 beforeDestroy 里清除状态并取消正在进行的请求
第三步,使用 AbortController 来终止还在挂起的网络请求,避免无谓的资源消耗
下面是改好的代码:
关键点解释一下:
shouldPoll 这个标志位必须每次递归前都判断,否则没意义。很多人只在最外层判断一次,但你这是递归啊兄弟,每一层都要知道要不要停。
AbortController 是现代浏览器提供的原生机制,axios 支持通过 signal 参数传入。一旦调用 abort(),所有绑定该 signal 的请求会立即以 AbortError 拒绝,不会发出去也不会卡着等超时。
为什么要在每次请求前新建 controller?因为老的已经被 abort 了不能复用,而且每个请求需要独立控制。
顺便提一嘴,长轮询其实不太适合用递归写法,容易爆栈(虽然一般不会),更好的方式是用循环 + 异步等待。不过你现在这个量级问题不大。
最后提醒一点:Vue 2 和 Vue 3 生命周期钩子名字不一样,如果你是 Vue 2 项目,记得把 beforeUnmount 改成 beforeDestroy。
照这么改完,页面切换时不会再报 undefined 的错了,所有资源都能正确释放。我上周刚在生产环境修过类似的坑,就是这种递归轮询没控制好,用户切十几个页面能攒出几百个并发请求……
核心就是加个
isPolling标志位,beforeDestroy里关掉它。每次递归或者错误重试前都先判断一下组件还在不在。不然页面切走了,this都没了你还去调this.poll(),不报错谁报错。