React页面刷新7次后无法加载数据的问题求助
页面刷新7次后无法加载数据的排查与解决
问题场景
React页面调用Express后端接口,后端再请求微服务接口获取数据,初始加载和前几次刷新正常,第7次刷新后无法加载数据,所有服务均无报错输出。
排查方向
- 数据库连接池耗尽:PostgreSQL连接池默认连接数通常为10左右,若每次请求只获取连接不释放,会快速耗尽连接资源,导致后续请求阻塞
- 请求链路错误未捕获:当前代码缺少完整的错误捕获逻辑,请求失败可能被静默忽略
no-cors模式限制:Express调用微服务时使用mode: 'no-cors'会限制响应读取权限,导致无法感知请求失败- 前端请求异常未处理:React的
fetch仅判断了响应状态为ok的情况,未处理网络错误或解析失败场景
代码中的潜在问题
1. 微服务数据库连接未释放
微服务的getHouses函数中,调用pool.connect()获取连接后未释放,每次请求都会占用一个连接:
pool.connect(); // 仅获取连接,未释放 const data = await pool.query(querySQL, [user, nomeQuartiere]);
当连接数达到池上限后,后续请求会被阻塞,无报错但无法获取数据。
2. Express请求错误未捕获且no-cors限制响应读取
Express接口中调用微服务的fetch使用mode: 'no-cors',此模式下浏览器会限制对响应的访问,即使请求失败也无法读取状态和内容;同时缺少错误捕获逻辑,异常会被静默忽略:
const response = await fetch('http://archiver:3002/v1/api/case-quartiere',{ mode: 'no-cors', // 限制响应读取 }).then((res) => {console.log(res);return res.json()});
3. React请求错误处理不完整
React的fetch仅处理了risposta.ok的情况,未处理非2xx状态码、网络错误或JSON解析失败的场景,导致请求失败时loading状态无法重置,页面一直处于加载状态或数据不更新:
fetch(`/api/getInfoHouses/${utente}/${id}`,{...}) .then((risposta) => { if(risposta.ok){ return risposta.json(); } // 缺少非ok状态的处理 }) .then((dati) => { // 缺少dati为undefined的处理 }) // 缺少catch捕获网络错误
4. 表单提交强制刷新页面
表单提交时调用refreshPage强制刷新页面,可能中断未完成的请求,积累连接占用:
<button type="submit" onClick={refreshPage}>...</button>
修复方案
1. 释放数据库连接
修改微服务的getHouses函数,使用pool.query自动管理连接(推荐),或手动获取连接后确保释放:
// 方案1:使用pool.query自动管理连接 const getHouses = async (token, nome_quartiere, username) => { const querySQL = "my query"; try{ const user = username; const nomeQuartiere = nome_quartiere; const data = await pool.query(querySQL, [user, nomeQuartiere]); return data.rows; }catch (err){ console.log("查询错误 ", err); throw err; // 抛出错误让上层处理 } } // 方案2:手动获取连接后释放 const getHouses = async (token, nome_quartiere, username) => { const querySQL = "my query"; let client; try{ client = await pool.connect(); const data = await client.query(querySQL, [user, nomeQuartiere]); return data.rows; }catch (err){ console.log("查询错误 ", err); throw err; } finally { if(client) client.release(); // 确保释放连接 } }
2. 移除no-cors并添加完整错误捕获
修改Express接口,移除mode: 'no-cors',修正header拼写错误为headers,并添加错误捕获:
app.get('/getInfoHouses/:utente/:id', cors(), async function(req, res){ console.log("in get info") const utente = req.params.utente; const id = req.params.id; try { const response = await fetch('http://archiver:3002/v1/api/case-quartiere',{ method:'POST', headers:{ 'Content-Type': 'application/x-www-form-urlencoded', // 匹配URLSearchParams的Content-Type }, body: new URLSearchParams({ token: userLog.get(utente), nome_quartiere: id, username: utente }), }); if (!response.ok) { throw new Error(`微服务请求失败: ${response.status}`); } const responseData = await response.json(); console.log("la risposta: ", responseData); if(responseData === "error"){ return res.status(400).send({ body: responseData }); }else{ return res.status(200).send({ body: responseData.data }); } } catch (err) { console.error("请求微服务出错: ", err); return res.status(500).send({ body: { message: "服务器内部错误" } }); } })
3. 完善React请求的错误处理
给React的fetch添加catch块,处理所有异常情况:
useEffect(()=>{ const utente = Cookies.get('idUtente') console.log("utente: ", utente) setLoading(true); fetch(`/api/getInfoHouses/${utente}/${id}`,{ method:'GET', credentials:"include", headers:{ // 修正为headers 'Content-Type':'application/json', } }) .then((risposta) => { if(!risposta.ok){ throw new Error(`请求失败: ${risposta.status}`); } return risposta.json(); }) .then((dati) => { console.log(dati.body) const message = dati.body.message; if(message == undefined){ const house = dati.body; setHouses(house); }else{ setError(message); } setLoading(false); }) .catch((err) => { console.error("获取数据出错: ", err); setError("无法加载数据,请稍后重试"); setLoading(false); }); },[])
4. 修改表单提交逻辑为异步请求
避免强制刷新页面,改用异步提交后更新本地状态:
// 添加删除处理函数 const handleDelete = async (e, house) => { e.preventDefault(); // 阻止默认表单提交 try { const response = await fetch('https://mydomain/api/deletecard', { method: 'POST', body: new URLSearchParams({ id_casa: house.id_casa, nome_quartiere: house.nome_quartiere }) }); if (response.ok) { // 更新本地状态,移除对应的房屋数据 setHouses(houses.filter(h => h.id_casa !== house.id_casa)); } else { setError("删除失败"); } } catch (err) { console.error("删除出错: ", err); setError("删除失败,请稍后重试"); } }; // 表单部分修改 <form onSubmit={(e) => handleDelete(e, house)}> <input type="hidden" name="id_casa" value={house.id_casa}></input> <input type="hidden" name="nome_quartiere" value={house.nome_quartiere}></input> <button type="submit"><img src={deleteButton}></img> </button> </form>
原始代码参考
React页面代码
export default function Dashboard(){ const [houses, setHouses] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(); const { id } = useParams(); console.log("id" , id); console.log(Cookies.get('cookie')) function refreshPage() { window.location.reload(false); } useEffect(()=>{ // const utente = localStorage.getItem('idUtente'); const utente = Cookies.get('idUtente') console.log("utente: ", utente) setLoading(true); fetch(`/api/getInfoHouses/${utente}/${id}`,{ method:'GET', credentials:"include", // mode:'no-cors', header:{ 'Content-Type':'application/json', } }).then((risposta) => { if(risposta.ok){ return risposta.json(); } }).then((dati) => { console.log(dati.body) const message = dati.body.message if(message == undefined){ const house = dati.body; setHouses(house); setLoading(false) }else{ setError(message); setLoading(false) } }) },[]) if(loading){ return < DashboardLoading mappa={mappa}/> } if(error != '' && error != undefined){ return( <> <Banner message={error}></Banner> </> ) }else{ return( <div className="hero"> <h1>Dashboard</h1> <h3 className="sottotitolo">Abitazioni del gruppo residenziale</h3> <div className="slider"> {houses.map(house => ( <div key={house.id_casa} className="card"> <div className="infoHouse"> <div className="id-abitazione"> <p>ID Abitazione: </p> <p>{house.id_casa}</p> </div> <div className="nome-abitazione"> <p>Nome Abitazione: </p> <p>{house.arduino}</p> </div> <div className="indirizzo-abitazione"> <p>Indirizzo: </p> <p>{house.indirizzo}</p> </div> <div className="carico-abitazione"> <p>Carico: </p> <p>{house.nome_carico}</p> </div> <div className="form"> <form method="POST" action="https://mydomain/api/deletecard"> <input type="hidden" name="id_casa" value={house.id_casa}></input> <input type="hidden" name="nome_quartiere" value={house.nome_quartiere}></input> <button type="submit" onClick={refreshPage}><img src={deleteButton}></img> </button> {/* <button type="submit"><img src={deleteButton}></img> </button> */} </form> </div> </div> <div className="container-img"> <img src={mappa} alt="mappa di distribuzione"></img> <div className="circle"> <span className=""></span> <p>{house.pannello}</p> </div> <div className="circle2"> <span className=""></span> <p>{house.caricoabitazione}</p> </div> <div className="circle3"> <span className=""></span> <p>{house.enel}</p> </div> <div className="circle4"> <span className=""></span> <p>{house.caricocomplesso}</p> </div> </div> </div> ))} </div> </div> ) } }
Express服务器接口代码
app.get('/getInfoHouses/:utente/:id',cors(), async function(req, res){ console.log("in get info") const utente = req.params.utente; const id = req.params.id; const response = await fetch('http://archiver:3002/v1/api/case-quartiere',{ method:'POST', header:{ // 'Access-Control-Allow-Origin':'*' }, body:new URLSearchParams({ token: userLog.get(utente), nome_quartiere: id, username: utente }), mode: 'no-cors', }).then((res) => {console.log(res);return res.json()}); console.log("la risposta: "); console.log(response) if(response === "error"){ return res.send({ body: response }).status(400) }else{ return res.send({ body: response.data }).status(200) } })
微服务接口代码
const getHouses = async (token, nome_quartiere, username) => { let houses; const querySQL = "my query"; try{ // const payload = await verifier.verify(token); // console.log("Token valido, Payload: ", payload); const user = username; const nomeQuartiere = nome_quartiere; pool.connect(); const data = await pool.query(querySQL, [user, nomeQuartiere]); houses = data.rows; return houses; }catch (err){ console.log("Token false ", err); // res.status(400).send("error") } }
数据库配置文件
const Pool = require("pg").Pool; const dotenv = require("dotenv"); dotenv.config() const pool = new Pool({ user:process.env.RDS_USERNAME, password:process.env.RDS_PASSWORD, host:process.env.RDS_HOSTNAME, port:process.env.RDS_PORT, database:process.env.RDS_DBNAME }); module.exports = pool;
内容的提问来源于stack exchange,提问作者DiegoM
相关产品推荐
相关产品推荐

