前言
在前端項目中,使用 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
更加清晰,方便中止和重置。
這樣寫就能保證:不管點多少次發送按鈕,每一次請求都能正確中止。