前言
在最近的專案重構過程中,我遇到了一個關於 Race Condition(競爭條件)的問題。當我找到解決方案後,分享給團隊成員時,發現這個處理方式對他們來說都是相對陌生的。這激起了團隊的興趣,他們邀請我在技術分享會上深入探討這個主題。
身為團隊的新成員,能有機會貢獻自己的經驗是很寶貴的。為了準備這次分享,我研究了相關資料,希望能夠完整地呈現這個議題。在這篇文章中,我將分享我的發現以及實務上的解決方案。
什麼是Race Condition (競態條件)
Race Condition 指的是在多個異步操作同時執行時,由於執行順序的不確定性,可能導致非預期的結果。在前端開發中,這種情況常見於:
- 多執行緒環境(如 Web Worker)
- 非同步程式設計(如async/await、Promise、setTimeout等)
我們用一個簡單的範例說明一下
假設某個網站的搜尋建議是透過 API 請求來獲取的:
- 你輸入 「weather」,網站發送請求A:
search?q=weather
(這個請求可能較慢)。 - 你立刻刪除並輸入 「news」,網站發送新的請求B:
search?q=news
(這個請求較快)。 - 但由於網路延遲,請求 B 先返回結果,接著請求 A 才返回。
- 如果網站沒有適當處理請求順序,最終顯示的搜尋建議可能是「weather」,但你的搜尋框其實已經輸入「news」。
那既然知道了什麼是Race Condition,那要怎麼解決這個問題呢?
如何解決Race Condition
當時遇到這個問題時,我依稀記得之前的助教Danny在之前鐵人賽文章有說過(已經出書了:React求職特訓營),再回去翻時果然在裡面就有講到這個。
在《React 求職特訓營》,提到了三種解決方式:
- 主動建立變數控制
- 使用AbortController方法
- 不要在useEffect中fetch資料,而是使用第三方套件
主動建立變數控制
這是最基本的解決方案,概念很簡單,就像我們平常控制 Loading畫面的方式:
1 | const [isLoading, setIsLoading] = useState(false); |
原來我們在日常開發中常用的載入狀態控制,就是解決 Race Condition 的其中一種方法!
使用AbortController方法
AbortController 是 JavaScript 原生 API,其目的是用於中止非同步的操作。
通常會搭配fetch或其他可取消的非同步任務使用,主要是透過AbortSignal 來控制請求的取消的。
下面是一個簡單的範例:
1 | function SearchBar() { |
我們可以把整個流程分成3個部分:
- 初始化階段
- 建立新的 AbortController 實例
- 自動產生內部的 signal object(負責追蹤取消狀態)
- 請求階段
- fetch 接收 signal 參數
- fetch 內部綁定 signal 的 abort 事件
- 監聽取消狀態
- 取消階段
- 當 abort() 被呼叫時
- signal.aborted 狀態變為 true
- fetch 立即停止請求並拋出 AbortError
我總結了一下它的優點與限制
優點:
- 原生支援,不需要額外套件
- 可以精確控制請求的取消時機
- 適合處理快速連續的 API 請求
限制:
- AbortController 實例無法重複使用
- 瀏覽器支援度需要考慮(可以查看 Can I use 了解支援情況)
- 雖然客戶端取消請求,伺服器端可能仍會繼續處理
雖然有以上的限制但不可否認還是一個很有用的技術。
不要在useEffect中fetch資料,而是使用第三方套件
這部分我就沒有研究,只能把找到的資料與大家做分享。
目前最受歡迎的兩個套件是:
- TanStack Query(React Query)
- SWR (Stale-While-Revalidate)
讓套件替你去處理這類的race condition以及一些cache的問題,也是目前許多團隊的選擇。
後記
沒想到在重構專案時遇到的 Race Condition 問題,意外地成為了一個寶貴的學習機會。透過這次的技術分享,不僅讓我更深入理解了這個議題,也讓我發現原來平常習以為常的程式碼(像是控制 loading 狀態)竟然蘊含著處理非同步競態的智慧。
最讓我印象深刻的是,這個議題引發了團隊內部的討論。每個人都分享了自己處理類似問題的經驗,讓我看到了同一個問題可以有不同的解決方案。這也提醒了我,在軟體開發中,沒有絕對的標準答案,重要的是找到最適合當前場景的解決方式。
延伸資源
https://ithelp.ithome.com.tw/articles/10327980
https://juejin.cn/post/7112699475327615006
https://www.51cto.com/article/799815.html
https://mini-ghost.dev/posts/tanstack-query-source-code-1