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

React调用Firestore参数引发竞态条件导致第三方API加载失败故障排查

问题根因
  • 核心错误:Apex、Chess两个API的请求回调中,错误使用了Overwatch请求对象request_O的状态做判断,没有用自身请求的状态。你的Chess请求哪怕返回200正常,只要request_O的状态不符合200-400区间,就不会进入数据渲染逻辑,这就是你URL正确但渲染失败的直接原因。
  • 次要问题:所有XMLHttpRequest请求直接写在组件顶层,只要组件状态更新(比如Firestore拉取到数据触发setState)就会重新执行组件逻辑,重复发送请求,这就是你遇到的无限执行、竞态问题的来源。
  • 不规范写法:React中直接调用document.getElementById操作DOM不符合声明式渲染的规范,全局声明的存储API数据的变量也会有跨实例污染的风险。
修复方案

首先把API请求逻辑封装到useEffect中,参数就绪后再发送请求,每个请求回调使用自身状态判断,数据存在组件state中,修复后核心代码如下:

// 新增各游戏数据state
const [overwatchStats, setOverwatchStats] = useState({
  mostPlayed: 'None',
  winRatio: 'None',
  compED: 'None',
  avgE: 'None'
})
const [apexStats, setApexStats] = useState({
  compRank: 'None',
  arenaRank: 'None',
  level: 'None',
  kills: 'None'
})
const [chessStats, setChessStats] = useState({
  wins: 'None',
  losses: 'None',
  winRate: 'None'
})

// Overwatch请求
useEffect(() => {
  if (!O_name || !O_platform || !O_region) return
  const O_url = `https://owapi.io/stats/${O_platform}/${O_region}/${O_name}`
  const request_O = new XMLHttpRequest()
  request_O.open('GET', O_url, true)
  request_O.onload = function () {
    if (request_O.status >= 200 && request_O.status < 400) {
      const data = JSON.parse(this.response)
      setOverwatchStats({
        mostPlayed: `Most Played: ${data.stats.top_heroes.competitive.played[0].hero}, ${data.stats.top_heroes.competitive.played[1].hero}, ${data.stats.top_heroes.competitive.played[2].hero}`,
        winRatio: `Win Ratio: ${(parseInt(data.stats.game.competitive[3].value) / parseInt(data.stats.game.competitive[1].value)).toFixed(3)}`,
        compED: `Competitive Elims/Deaths: ${(parseInt(data.stats.combat.competitive[4].value) / parseInt(data.stats.combat.competitive[3].value)).toFixed(3)}`,
        avgE: `Avg. Elims / 10 Minutes: ${data.stats.average.competitive[3].value}`
      })
    }
  }
  request_O.send()
}, [O_name, O_platform, O_region])

// Apex请求
useEffect(() => {
  if (!A_name || !A_platform) return
  const A_url = `https://api.mozambiquehe.re/bridge?version=5&platform=${A_platform}&player=${A_name}&auth=...`
  const request_A = new XMLHttpRequest()
  request_A.open('GET', A_url, true)
  request_A.onload = function () {
    // 用自身request_A的状态判断
    if (request_A.status >= 200 && request_A.status < 400) {
      const data = JSON.parse(this.response)
      setApexStats({
        compRank: `Competitive Rank : ${data.global.rank.rankName} -- ${data.global.rank.rankScore}`,
        arenaRank: `Arena Rank : ${data.global.arena.rankName} -- ${data.global.arena.rankScore}`,
        level: `Level : ${data.global.level}`,
        kills: `Kills : ${data.total.kills.value}`
      })
    }
  }
  request_A.send()
}, [A_name, A_platform])

// Chess请求
useEffect(() => {
  if (!chessName) return
  const C_url = `https://api.chess.com/pub/player/${chessName}/stats`
  const request_C = new XMLHttpRequest()
  request_C.open('GET', C_url, true)
  request_C.onload = function () {
    // 用自身request_C的状态判断
    if (request_C.status >= 200 && request_C.status < 400) {
      const data = JSON.parse(this.response)
      setChessStats({
        wins: `Wins : ${data.chess_rapid.record.win}`,
        losses: `Losses : ${data.chess_rapid.record.loss}`,
        winRate: `Win rate : ${(parseInt(data.chess_rapid.record.win) / parseInt(data.chess_rapid.record.loss)).toFixed(3)}`
      })
    }
  }
  request_C.send()
}, [chessName])

// 渲染部分直接读state即可,无需操作DOM
return(
  <div className="apiDisplays">
    <div id="nameDisplay">
      Meet {name}!<br/><br/>
    </div>
    <div id="gamesPlayedDisplay">
      {name} plays these games: <br/>
      {game1} <br/> {game2} <br/> {game3}
      <br/><br/>
    </div>
    <div id="apiDisplay">
      Avaliable stats for {name} <br/><br/>
    </div>
    <div>
      <h1>Overwatch Stats</h1>
      <p>{overwatchStats.mostPlayed}</p>
      <p>{overwatchStats.winRatio}</p>
      <p>{overwatchStats.compED}</p>
      <p>{overwatchStats.avgE}</p> 
    </div>
    <div>
      <h1>Apex Stats</h1>
      <p>{apexStats.compRank}</p>
      <p>{apexStats.arenaRank}</p>
      <p>{apexStats.level}</p>
      <p>{apexStats.kills}</p>
    </div>
    <div>
      <h1>Chess.com Stats</h1>
      <p>{chessStats.wins}</p>
      <p>{chessStats.losses}</p>
      <p>{chessStats.winRate}</p>
    </div>
  </div>
)

删除组件顶层原来写的所有请求、全局变量、DOM操作逻辑即可。

内容的提问来源于stack exchange,提问作者Cole

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 15:21:01