【问题标题】:resolving race condition on API call解决 API 调用的竞争条件
【发布时间】:2020-03-06 04:49:48
【问题描述】:

我遇到了一个似乎是由于异步调用引起的问题。我有一个进行 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 调用。但是,似乎第一个 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);

【问题讨论】:

    标签: reactjs redux axios race-condition use-effect


    【解决方案1】:

    问题的根源是你在这里发出请求,而不是

    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
            })
    }
    

    【讨论】:

    • 这行得通,只是结果 fn 不能在 promise 的解析中,必须在外面。另外,我意识到我可以将 props.accountId 放在 useEffect 挂钩的依赖数组中,然后在运行 useEffect 挂钩中的 fn 之前使用 if 语句检查 props.accountId。两种方式都有效。
    • True 两者都适用于快乐路径,但是当登录请求失败并且您已经将它们路由到仪表板路由时呢?您是否要将它们路由回去并在登录页面上显示错误消息?我认为在将它们路由到另一个位置之前,最好在 submitLogin 中捕获返回的承诺。这样您就可以在那里显示错误文本,并且在没有成功登录的情况下不允许他们继续
    猜你喜欢
    • 2019-08-20
    • 1970-01-01
    • 1970-01-01
    • 2018-01-19
    • 2015-08-13
    • 2013-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多