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

如何不使用useState在循环中存储API请求响应到全局变量?

解决方案

核心问题分析

  1. 异步请求时序问题:fetch是异步操作,循环中同步执行rec.push()时,API响应还未返回,导致imdbHolder始终是初始值或被后续请求覆盖。
  2. 全局变量覆盖:全局变量会被所有异步回调依次赋值,最终仅保留最后一次请求的结果。
  3. 重复请求触发:直接在渲染相关函数(如formatRec)中调用API,会因组件重复渲染触发多次请求,耗尽API配额。

修复方案

将API请求逻辑从渲染函数中剥离,在获取到Flask返回的电影数据后,一次性批量请求第三方API,通过Promise.all等待所有请求完成后,将关联好的数据存入状态,避免重复请求和时序问题。

修改后的完整代码

import { useState } from 'react';

const App = () => {
    const [prediction, setPrediction] = useState(null);
    // 新增状态存储关联后的推荐数据
    const [recommendationsWithDetails, setRecommendationsWithDetails] = useState([]);

    const onSubmit = async e => {
        e.preventDefault();

        const formData = new FormData();
        formData.append("target_user", document.getElementById("target_user").value)
        formData.append("no_of_highest", document.getElementById("no_of_highest").value)
        formData.append("no_of_similar_users", document.getElementById("no_of_similar_users").value)
        formData.append("no_of_movies", document.getElementById("no_of_movies").value)

        fetch('http://localhost:5000/upload', {
            method: 'POST',
            body: formData
        })
        .then(res => res.json())
        .then(data => {
            setPrediction(data);
            // 获取电影数据后,批量请求第三方API
            fetchRecommendationDetails(data);
        });
    };

    // 批量请求第三方API并关联电影数据
    const fetchRecommendationDetails = async (predictionData) => {
        const noMovies = parseInt(document.getElementById("no_of_movies").value);
        const movieList = predictionData.data.Recommendatons[0];
        const fetchTasks = [];

        for (let i = 1; i <= noMovies; i++) {
            const movieName = movieList[i];
            // 替换为实际IMDb API请求
            const task = fetch(`https://catfact.ninja/fact`)
                .then(res => res.json())
                .then(apiRes => ({
                    movie: movieName,
                    detail: apiRes.fact // 实际项目中替换为IMDb返回的所需字段(如电影ID)
                }));
            fetchTasks.push(task);
        }

        // 等待所有请求完成,获取关联后的结果
        const results = await Promise.all(fetchTasks);
        setRecommendationsWithDetails(results);
    };

    const formatTop = () => {
        let top = [];
        const count = parseInt(document.getElementById("no_of_highest").value);
        for (let i = 0; i <= count; i++) {
            top.push(<p key={i}>{prediction.data.Users_Top_Movies[0][i]}</p>);
        }
        return top;
    };

    const formatSim = () => {
        let sim = [];
        const count = parseInt(document.getElementById("no_of_similar_users").value);
        for (let i = 0; i <= count - 1; i++) {
            sim.push(<p key={i}>User #{prediction.data.Similar_Users[i]}, separated by a distance of {prediction.data.Sim_User_distances[i]}</p>);
        }
        return sim;
    };

    // 直接使用已关联好的状态数据渲染
    const formatRec = () => {
        return recommendationsWithDetails.map((item, index) => (
            <p key={index}>{item.movie} - {item.detail}</p>
            {/* 实际IMDb链接写法:
            <p key={index}>
                {item.movie} - <a href={`https://www.imdb.com/title/${item.detail}`}>IMDb link</a>
            </p>
            */}
        ));
    };

    return (
        <div className='App'>
            <form onSubmit={onSubmit}>
                <div className='custom-file'>
                    <label for="target_user">target_user</label>
                    <input type="number" id="target_user" name="target_user" />
                    <br />
                    <br />
                    <label for="no_of_highest">no_of_highest_rated_movies_by_target_user</label>
                    <input type="number" id="no_of_highest" name="no_of_highest" />
                    <br />
                    <br />
                    <label for="no_of_similar_users">no_of_similar_users</label>
                    <input type="number" id="no_of_similar_users" name="no_of_similar_users" />
                    <br />
                    <br />
                    <label for="no_of_movies">no_of_movies_to_recommend</label>
                    <input type="number" id="no_of_movies" name="no_of_movies" />
                </div> 
                <input
                    type='submit'
                    value='Submit'
                    className='btn btn-primary btm-block mt-4'
                />
            </form>
            <h1>Users Top Movies</h1>
            { prediction && formatTop() }
            <br />
            <h1>Users Most Similar to Target User</h1>
            { prediction && formatSim() }
            <br />
            <h1>Recommendations Based on Similar Users</h1>
            { prediction && recommendationsWithDetails.length > 0 && formatRec() }
        </div>
    );
};

export default App;

关键优化点

  1. 批量异步处理:用Promise.all统一管理多个API请求,确保所有数据返回后再更新状态,避免时序问题。
  2. 状态驱动渲染:将关联后的电影数据存入recommendationsWithDetails状态,渲染时直接读取状态,避免重复触发API请求。
  3. 避免全局变量:每个请求结果直接与对应电影绑定,不存在变量覆盖问题。

内容的提问来源于stack exchange,提问作者Marcus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 09:37:03