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 中的竞争条件
React Jest:如何模拟返回数据的异步 API 调用?