简体   繁体   中英

React - Async calls with an unexpected return order

I have a react component (let's call it Logs ) which includes another react component (let's call it DateChanger ) which, let's say for the sake of this example, only serves to change the date of the parent component. And when Logs gets the date change, it makes an async call to update it's own state with data from a server:

class Logs extends React.Component {
    ....

    onDateChange(newDate) {
        this.setState({loading: true, date: newDate});
        asyncCall(newDate)
            .then(results => {
                this.setState({data: results, loading: false})
            });
    }

    render() {
        return (
            ....
            <DateChanger onChange={this.onDateChange}>
            ....
        )
    }
}

The problem I'm having is that, if someone changes the date twice in quick succession, the rendered 'data' is not always from the correct date.

So what I mean specifically is, in this example, DateChanger has a button to change the date by 1 day, forward and backward. So today is the 5th, and someone can click the back button on the date changer to request data from the 4th, and then click it again to request data from the 3rd.

Some of the time, the asyncCall returns the results in the wrong order -- you click back once and request the 4th, then click it again and request the 3rd before the 4th is returned and occassionally the server returns the data for the 3rd, and then the data for the 4th, and it renders to the user the data for the 4th because that was the most recent .then processed.

What is the React way to make sure that it renders the data from the 3rd instead of the 4th, regardless of which server call returns first?

[edit] this is not about using the setState callback. If I moved my asyncCall inside the setState callback, this problem would still remain. (Although if you want to suggest that I should be moving my asyncCall inside the setState callback, I will gladly take that advice -- it just doesn't solve my problem)

For a quick and dirty solution, you could check if the response you get is the one your component is looking for.

onDateChange(newDate) {
    this.setState({loading: true, date: newDate});
    asyncCall(newDate)
        .then(results => {
            // Update state only if we are still on the date this request is for.
            if (this.state.date === newDate) {
                this.setState({data: results, loading: false})
            }
        });
}

But a solution with better separation of concerns and higher reusability could look like this.

// Request manager constructor.
// Enables you to have many different contexts for concurrent requests.
const createRequestManager = () => {
    let currentRequest;

    // Request manager.
    // Makes sure only last created concurrent request succeeds.
    return promise => {
        if (currentRequest) {
            currentRequest.abort();
        }

        let aborted = false;

        const customPromise = promise.then(result => {
            if (aborted) {
                throw new Error('Promise aborted.');
            }

            currentRequest = null;

            return result;
        });

        customPromise.abort = () => {
            aborted = true;
        };

        return customPromise;
    };
};

class Logs extends React.Component {
    constructor(props) {
        super(props);

        // Create request manager and name it “attemptRequest”.
        this.attemptRequest = createRequestManager();
    }

    onDateChange(newDate) {
        this.setState({loading: true, date: newDate});
        // Wrap our promise using request manager.
        this.attemptRequest(asyncCall(newDate))
            .then(results => {
                this.setState({data: results, loading: false})
            });
    }

    render() {
        return (
            ....
            <DateChanger onChange={this.onDateChange}>
            ....
        )
    }
}

Have in mind that we are not actually aborting the request, only ignoring its result.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM