You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 22:56:57