You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 22:40:40