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();}// 创建新的 controllercontroller.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
更加清晰,方便中止和重置。
这样写就能保证:不管点多少次发送按钮,每一次请求都能正确中止。