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

Posted

技术标签:

【中文标题】React 中的竞争条件; API 调用、数据库调用【英文标题】:Race condition in React; API calls, database call 【发布时间】:2022-01-11 03:37:41 【问题描述】:

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

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

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

我已将我的问题缩小到这个:这个脚本应该按顺序运行一次,但它现在在添加数据库拉取后无限运行。这意味着每个 API 查询都会运行不止一次。因为从数据库获取参数存在延迟,所以脚本会尝试运行不带参数的 API 查询(因此不正确)。这会返回一个错误并阻止第三个 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.***royale.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,为什么我的程序返回此查询的状态不充分?

【问题讨论】:

【参考方案1】:

事实上,您的 API 调用每次渲染组件时都会执行,因为它们位于函数体中。 React 组件只是普通的旧函数,因此每次 React 渲染您的组件时(阅读:每次 React 调用您的函数时),您的组件将运行所有 API 调用,而不管参数的状态如何。那很糟!至少,您应该将 API 调用包装在 useEffect 中,并在从 API 获取之前测试您是否已经拥有所需的参数:

// this is pseudocode, hopefully you get the idea
    
React.useEffect(() => 
  if (dbParameters) 
  // do your fetching
  
, [dbParameters]);

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

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

【讨论】:

我编辑了我的帖子以显示一些修复。我现在“测试 [I] 在 [I] 从 API 获取之前 [I] 是否已经具有 [I] 需要的参数”。我会尝试将这些 if 转移到我的使用效果中;现在,我对您的回复中应该用什么值代替 dbParameters 感到困惑。我的代码很有趣,因为现在 API 调用中没有错误,但第三个 API 仍然没有响应,这意味着它打印出“没有输入 c if 语句”。鉴于我的第三个 API 的 url 字符串是正确的,为什么它没有返回有效的响应状态? 我的伪代码中的 dbParameters 表示您的 API 调用所依赖的任何值,因此在您的情况下,无论您使用什么来构造 API url - 0_name、0_platform、0_region、A_platform 等。

以上是关于React 中的竞争条件; API 调用、数据库调用的主要内容,如果未能解决你的问题,请参考以下文章

`render` 和 `componentDidMount` 之间 Store 中的竞争条件

REST API 中的竞争条件

React Jest:如何模拟返回数据的异步 API 调用?

React Hooks-处理异步api调用

React Router 根据组件的 API 调用有条件地导航到不同的页面

如何在 REACT JS API 调用中迭代 JSON 嵌套数组?