React异步请求竞态问题:快速点击按钮状态不符
问题解决:快速连续点击按钮后内容显示不匹配
问题原因
快速连续点击按钮时,每次点击触发的异步请求(fetch+耗时的getLines)相互独立。由于异步操作完成时间不确定,先发起的长请求(如300长度)可能比后发起的短请求(如100长度)更晚完成,导致后完成的请求覆盖已更新的状态,最终显示内容与最后一次点击的按钮不匹配。
解决方案
方案1:用AbortController中断旧请求
通过AbortController在新请求发起时中断未完成的旧请求,避免旧请求结果干扰状态。修改后代码如下:
import { useState, useEffect } from "react"; import getLines from "./getline"; export default function App() { const [text, setText] = useState<string>(""); const [arr, setArr] = useState<string[] | "error" | undefined>([]); const [time, setTime] = useState<100 | 200 | 300 | "un">("un"); const [pending, setPending] = useState(false); useEffect(() => { const abortController = new AbortController(); const signal = abortController.signal; if (time !== "un") { (async () => { setPending(true); try { let res = await fetch( `https://random-word-genrator.vercel.app/words?w=${time}`, { signal } ); if (signal.aborted) return; let { response } = await res.json(); let a = await getLines(800, response, "1.2rem", "3px"); if (signal.aborted) return; setArr(a); setText(response); } catch (err) { if (!(err instanceof DOMException && err.name === "AbortError")) { setArr("error"); } } finally { setPending(false); } })(); } return () => abortController.abort(); }, [time]); return ( <div> <div style={{ display: "flex", gap: "1rem" }}> <div onClick={() => setTime(100)}>100</div> <div onClick={() => setTime(200)}>200</div> <div onClick={() => setTime(300)}>300</div> </div> {pending ? ( <div>loading</div> ) : ( <div> <div>value:={text}</div> <div>array:={JSON.stringify(arr)}</div> <div>length:={text.split(" ").length}</div> </div> )} </div> ); }
方案2:追踪当前请求标识
如果getLines是无法中断的纯计算操作,可记录当前请求的time值,异步操作完成后检查是否与最新time一致,仅一致时更新状态:
import { useState, useEffect } from "react"; import getLines from "./getline"; export default function App() { const [text, setText] = useState<string>(""); const [arr, setArr] = useState<string[] | "error" | undefined>([]); const [time, setTime] = useState<100 | 200 | 300 | "un">("un"); const [pending, setPending] = useState(false); useEffect(() => { if (time !== "un") { const currentRequestTime = time; (async () => { setPending(true); try { let res = await fetch( `https://random-word-genrator.vercel.app/words?w=${time}` ); let { response } = await res.json(); let a = await getLines(800, response, "1.2rem", "3px"); if (currentRequestTime === time) { setArr(a); setText(response); } } catch (err) { setArr("error"); } finally { if (currentRequestTime === time) { setPending(false); } } })(); } }, [time]); return ( <div> <div style={{ display: "flex", gap: "1rem" }}> <div onClick={() => setTime(100)}>100</div> <div onClick={() => setTime(200)}>200</div> <div onClick={() => setTime(300)}>300</div> </div> {pending ? ( <div>loading</div> ) : ( <div> <div>value:={text}</div> <div>array:={JSON.stringify(arr)}</div> <div>length:={text.split(" ").length}</div> </div> )} </div> ); }
方案说明
- 方案1适用于可中断的异步操作(如网络请求),能直接终止旧请求,节省资源。
- 方案2适用于无法中断的异步操作(如纯计算任务),通过请求标识过滤无效状态更新。
内容的提问来源于stack exchange,提问作者Pradeep Bisht
相关产品推荐
相关产品推荐

