Vue + fetchEventSource 使用 AbortController 遇到的“只能中止一次”问题解析与解决便捷的方案
前言
在前端项目中,使用 SSE(Server-Sent Events) 长连接去获取实时消息已经很常见了。像 fetchEventSource 这种封装好的工具,可以帮助我们轻松处理流式请求。
不过在实践中,我遇到了一个奇怪的问题:点击按钮触发 SSE 请求时,controller.abort() 只能生效一次,第二次再触发就完全没用了。本文记录一下排查和解决过程。
问题复现
假设有如下代码:
const controller = new AbortController();
const onSubmit = () =>
{
// 中止上一轮消息请求
controller.abort();
ElMessage.success("中止");
// 建立新的消息请求
fetchEventSource(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "小明"
}),
openWhenHidden: true, // 窗口不可见时保持连接
signal: controller.signal, // 请求控制器:用于中止sse请求的
onmessage(ev) {
console.log("收到消息", ev.data);
},
});
};
第一次点击按钮时,能成功中止请求;
但第二次点击时,请求却再也中止不了了。
问题原因
问题的关键在于 AbortController 的 signal 只能使用一次。
controller.abort()调用后,controller.signal就已经被标记为 aborted。- 下一次再传入同一个
signal给fetchEventSource,它会发现这个 signal 已经失效,自然就无法再中止。
也就是说:AbortController 不能复用,每次请求都必须创建新的实例。
解决方案
在 Vue 组件中,可以把 controller 用 ref 管理,每次请求时都先 abort 上一个,再创建一个新的。
正确写法
import { ref
} from "vue";
const controller = ref(new AbortController());
const onSubmit = () =>
{
// 先中止上一次请求(如果存在)
if (controller.value) {
controller.value.abort();
}
// 创建新的 controller
controller.value = new AbortController();
fetchEventSource(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
signal: controller.value.signal, // 用 ref 给
onmessage(ev) {
console.log("收到消息", ev.data);
},
onerror(err) {
console.error("错误:", err);
}
});
};
总结
AbortController是一次性消耗品,不能复用。- 每次请求前必须
controller = new AbortController()。 - 在 Vue 中用
ref管理controller更加清晰,方便中止和重置。
这样写就能保证:不管点多少次发送按钮,每一次请求都能正确中止。
浙公网安备 33010602011771号