首页 > 解决方案 > 解决 API 调用的竞争条件

问题描述

我遇到了一个似乎是由于异步调用引起的问题。我有一个进行 API 调用并推送到仪表板页面的操作。该 API 调用还会state.account.id根据它返回的响应进行更新:

const submitLogin = e => {
        e.preventDefault();
        props.loginAndGetAccount(credentials);
        props.history.push('/protected');
        e.target.reset();
    }

loginAndGetAccount来自这个动作:

export const loginAndGetAccount = credentials => dispatch => {
    dispatch({ type: GET_ACCOUNT_START })
    axios
        .post('https://foodtrucktrackr.herokuapp.com/api/auth/login/operators', credentials)
        .then(res => {
            console.log(res);
            dispatch({ type: GET_ACCOUNT_SUCCESS, payload: res.data.id })
            localStorage.setItem("token", res.data.token)
        })
        .catch(err => console.log(err));
}

在仪表板页面上,我设置了 useEffect 以根据state.account.id. 但是,似乎第一个 API 调用是在响应返回和更新之前推送到仪表板页面state.account.id。因此,当在那里进行第二次 API 调用时,它会state.account.id以未定义的形式传递给该动态 API 调用,这当然会导致调用失败。我该如何解决这个问题?这是正在发生的事情:

const Dashboard = props => {
    const [accountInfo, setAccountInfo] = useState({});

    useEffect(() => {
            console.log(props.accountId);
            axiosWithAuth()
                .get(`/operator/${props.accountId}`)
                .then(res => {
                    console.log(res);
                })
                .catch(err => console.log(err));
    }, [])

    return (
        <div>
            <h1>This is the Dashboard component</h1>
        </div>
    )
}

const mapStateToProps = state => {
    return {
        accountId: state.account.id
    }
}

export default connect(mapStateToProps, {})(Dashboard);

在此处输入图像描述

标签: reactjsreduxaxiosrace-conditionuse-effect

解决方案


问题的根源是您在这里提出请求,但不是

export const loginAndGetAccount = credentials => dispatch => {
    dispatch({ type: GET_ACCOUNT_START })
    axios
        .post('https://foodtrucktrackr.herokuapp.com/api/auth/login/operators', credentials)
        .then(res => {
            console.log(res);
            dispatch({ type: GET_ACCOUNT_SUCCESS, payload: res.data.id })
            localStorage.setItem("token", res.data.token)
        })
        .catch(err => console.log(err));
}

在导航到下一页之前等待它在此处完成

const submitLogin = e => {
        e.preventDefault();
        props.loginAndGetAccount(credentials);
        props.history.push('/protected');
        e.target.reset();
    }

解决这个问题的最快方法是从该承诺中返回承诺loginAndGetAccount,然后props.history.push在该承诺的解决中......

像这样:

export const loginAndGetAccount = credentials => dispatch => {
    dispatch({ type: GET_ACCOUNT_START })
    // return the promise here
    return axios
        .post('https://foodtrucktrackr.herokuapp.com/api/auth/login/operators', credentials)
        .then(res => {
            console.log(res);
            dispatch({ type: GET_ACCOUNT_SUCCESS, payload: res.data.id })
            localStorage.setItem("token", res.data.token)
        })
        .catch(err => console.log(err));
}

...


const submitLogin = e => {
    e.preventDefault();
    props.loginAndGetAccount(credentials)
        .then(() => {
            // so that you can push to history when it resolves (the request completes)
            props.history.push('/protected');
            e.target.reset();
        }
        .catch(e => {
            // handle the error here with some hot logic
        })
}

推荐阅读