React 中的竞争条件; API 调用,数据库调用

Race condition in React; API calls, database call

我在网站中有一个页面,旨在显示三个 API 的 API 数据。当我单击此页面时,前两个 API 加载,但第三个不加载。

每个API使用一组参数。例如,第一个 API 在 url 中有平台、区域和用户名。参数存储在我为此项目创建的 firestore 数据库中。

当我开始这个脚本时,我最初对参数进行了硬编码,所有三个 API 都没有问题。但是,当我开始从 firestore 数据库中提取参数时,我创建了一个竞争条件。

我已将我的问题缩小为:此脚本应该按顺序 运行 一次,但现在在添加数据库拉取后它会 运行 无限次。这意味着每个 API 查询不止一次 运行。因为从数据库获取参数有延迟,脚本会尝试 运行 API 不带参数的查询(因此是不正确的)。这是一个 returns 错误并阻止第三个 API 加载。

我在下面包含了我的脚本,以及我的网站和控制台的屏幕截图。

// import React from "react";
import React, { useState, useEffect, useMemo, useRef } from "react";
// import "./Statsview.css";

import {database} from "./firebase";

import PersonIcon from '@material-ui/icons/Person';
import ForumIcon from "@material-ui/icons/Forum";
import IconButton  from "@material-ui/core/IconButton";
import ArrowBackIosIcon from "@material-ui/icons/ArrowBackIos";
import {Link, useHistory, useParams} from "react-router-dom";
import { People } from "@material-ui/icons";

console.log('starting script');


var O_mostPlayed;
var O_winRatio;
var O_compED;
var O_avgE;

var A_compRank;
var A_arenaRank;
var A_level;
var A_kills;

var C_wins;
var C_losses;
var C_winRate;


function Statsview( {backButton} ) {
    // getting api info from our db
    const urlParams = new URLSearchParams(window.location.search);
    const name = urlParams.get('name');
    const chessName = urlParams.get('chess');

    const [game1, setgame1] = useState("");
    const [game2, setgame2] = useState("");
    const [game3, setgame3] = useState("");

    const [O_name, setO_name] = useState("");
    const [O_platform, setO_platform] = useState("");
    const [O_region, setO_region] = useState("");
    const [A_name, setA_name] = useState("");
    const [A_platform, setA_platform] = useState("");
    const [C_name, setC_name] = useState("");

    const [didO, setset_didO] = useState(false);
    const [didA, setset_didA] = useState(false);
    const [didC, setset_didC] = useState(false);

    
    useEffect(() => {
        database.collection('USERS').where('name','==',name).get().then(snapshot => {
            snapshot.forEach(doc => {
                const data = doc.data()
                setgame1(data.game1);
                setgame2(data.game2);
                setgame3(data.game3);
                setO_name(data.overwatch);
                setO_platform(data.overwatchPlatform);
                setO_region(data.overwatchRegion);
                setA_name(data.apexLegends);
                setA_platform(data.apexLegendsPlatform);
                // setC_name(data.chess);
            })
        }).catch(error => console.log(error))      
    },[]);

    var O_url = 'https://owapi.io/stats/' + O_platform + '/' + O_region + '/' + O_name;
    var A_url = 'https://api.mozambiquehe.re/bridge?version=5&platform=' + A_platform + '&player=' + A_name + '&auth=...';
    // var C_url = 'https://api.chess.com/pub/player/' + chessName + '/stats';
    var C_url = 'https://api.chess.com/pub/player/cnewby5283/stats';
    console.log(C_url);

    // console.log(O_url)
    var request_O = new XMLHttpRequest();
    var request_A = new XMLHttpRequest();
    var request_C = new XMLHttpRequest();
    // API queries
    request_O.open('GET', O_url, true);
    request_A.open('GET', A_url, true);
    request_C.open('GET', C_url, true);
    // potential API queries
    // request.open('GET', 'https://api.clashroyale.com/v1/players/%239CCUURQVJ', true);
    // request.setRequestHeader("authorization","Bearer [api key for specific IP]");
    console.log('got queries');

    if (O_name.localeCompare("") != 0 && O_platform.localeCompare("") != 0 && O_region.localeCompare("") != 0) {
        console.log("O_url: ", O_url)
        request_O.onload = function () {
            var data = JSON.parse(this.response);
            if (request_O.status >= 200 && request_O.status < 400) {
                O_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;
                O_winRatio = "Win Ratio: " +  (parseInt(data.stats.game.competitive[3].value) / parseInt(data.stats.game.competitive[1].value)).toFixed(3);
                O_compED = "Competitive Elims/Deaths: " +  (parseInt(data.stats.combat.competitive[4].value) / parseInt(data.stats.combat.competitive[3].value)).toFixed(3);
                O_avgE = "Avg. Eims / 10 Minutes: " +  data.stats.average.competitive[3].value;
                document.getElementById("O_1").innerHTML = O_mostPlayed;
                document.getElementById("O_2").innerHTML = O_winRatio;
                document.getElementById("O_3").innerHTML = O_compED;
                document.getElementById("O_4").innerHTML = O_avgE;
            } else {
                return (
                    <marquee>
                        API Request Failed
                    </marquee>
                )
            }
            // didO = true;
        }

        request_O.send();
    }
    if (A_name.localeCompare("") != 0 && A_platform.localeCompare("") != 0) {
        console.log("A_url: ", A_url)
        request_A.onload = function () {
            var data = JSON.parse(this.response);
            if (request_O.status >= 200 && request_O.status < 400) {
                A_compRank = "Competitive Rank : " + data.global.rank.rankName + " -- " + data.global.rank.rankScore;
                A_arenaRank = "Arena Rank : " + data.global.arena.rankName + " -- " + data.global.arena.rankScore;
                A_level = "Level : " + data.global.level;
                A_kills = "Kills : " + data.total.kills.value;
                document.getElementById("A_1").innerHTML = A_compRank;
                document.getElementById("A_2").innerHTML = A_arenaRank;
                document.getElementById("A_3").innerHTML = A_level;
                document.getElementById("A_4").innerHTML = A_kills;
                
            } else {
                return (
                    <marquee>
                        API Request Failed
                    </marquee>
                )
            }
            // didA = true;
        }
        request_A.send();
    }
    if (chessName.localeCompare("") != 0) {
        console.log("C_url: ", C_url)
        request_C.onload = function () {
            // console.log("test chess query below 1");
            var data = JSON.parse(this.response);
            if (request_O.status >= 200 && request_O.status < 400) {
                console.log("entered c if statement")
                C_wins = "Wins : " + data.chess_rapid.record.win;
                C_losses = "Losses : " + data.chess_rapid.record.loss;
                C_winRate = "Win rate : " + (parseInt(data.chess_rapid.record.win) / parseInt(data.chess_rapid.record.loss)).toFixed(3);
                document.getElementById("C_1").innerHTML = C_wins;
                document.getElementById("C_2").innerHTML = C_losses;
                document.getElementById("C_3").innerHTML = C_winRate;
                
            } else {
                console.log("didnt enter c if statement")
                return (
                    <marquee>
                        API Request Failed
                    </marquee>
                )
            }
            // didC = true;
        }
        
        request_C.send();
    }

    return(
        <div class="apiDisplays">
            <div id="nameDisplay">
                Meet {name}!<br/><br/>
            </div>
            <div id="gamesPlayedDisplay">
                {/* show games */}
                {name} plays these games: <br/>
                {game1} <br/> {game2} <br/> {game3}
                <br/><br/>
            </div>
            <div id="apiDisplay">
                {/* shows api data */}
                Avaliable stats for {name} <br/><br/>
            </div>
            <div>
                <h1>Overwatch Stats</h1>
                <p id="O_1">None</p>
                <p id="O_2">None</p>
                <p id="O_3">None</p>
                <p id="O_4">None</p> 
            </div>
            <div>
                <h1>Apex Stats</h1>
                <p id="A_1">None</p>
                <p id="A_2">None</p>
                <p id="A_3">None</p>
                <p id="A_4">None</p>
            </div>
            <div>
                <h1>Chess.com Stats</h1>
                <p id="C_1">None</p>
                <p id="C_2">None</p>
                <p id="C_3">None</p>
            </div>
        </div>
    )
}

export default Statsview


编辑: 我在每个 API 调用之前包含了 if 语句,现在我没有收到任何错误。

然而,第三个 API 仍然没有显示。这很有趣,因为我在 XMLHTTPRequest 中发送的 url 是正确的,如屏幕截图所示。 (我在第三个 if 语句中输出 url)。

如果我为 API 查询发送正确的 url,为什么我的程序返回此查询的状态不充分?

事实上,您的 API 调用会在每次呈现组件时执行 因为它们在函数的主体中。 React 组件只是普通的旧函数,所以每次 React 渲染你的组件时(阅读:每次 React 调用你的函数),你的组件将 运行 所有 API 调用而不管参数的状态.那很糟!至少,您应该将 API 调用包装在 useEffect 中,并在从 API:

获取之前测试您是否已经拥有所需的参数
// this is pseudocode, hopefully you get the idea
    
React.useEffect(() => {
  if (dbParameters) {
  // do your fetching
  }
}, [dbParameters]);

您至少对这个概念有点熟悉,因为您已经为数据库参数做过了!

我强烈建议通读 this article 几遍,它是处理 useEffect 和获取数据时的宝贵资源。