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 和获取数据时的宝贵资源。
我在网站中有一个页面,旨在显示三个 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 和获取数据时的宝贵资源。