简体   繁体   English

反应-异步调用具有意外的返回顺序

[英]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. 我有一个react组件(我们称它为Logs ),其中包括另一个react组件(我们称它为DateChanger ),就本例而言,它仅用于更改父组件的日期。 And when Logs gets the date change, it makes an async call to update it's own state with data from a server: 当Logs获得日期更改时,它将进行一次异步调用,以使用服务器中的数据更新其自身的状态:

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. 因此,我的具体意思是,在此示例中,DateChanger具有一个按钮,可以将日期向前和向后更改1天。 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. 因此,今天是5号,有人可以单击日期更改器上的后退按钮以从4号请求数据,然后再次单击它以从3号请求数据。

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. asyncCallasyncCall会以错误的顺序返回结果-您单击一次返回并请求第4个,然后再次单击它并请求第3个,然后返回第4个,有时服务器会返回第3个的数据,并且然后第4的数据,因为这是最近它呈现给用户第四届数据.then进行处理。

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? 无论哪个服务器调用首先返回,React如何确保从第三位而不是第四位呈现数据?

[edit] this is not about using the setState callback. [edit]这与使用setState回调无关。 If I moved my asyncCall inside the setState callback, this problem would still remain. 如果将asyncCall移到setState回调中,则此问题仍然存在。 (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) (尽管如果您想建议我将asyncCall移到setState回调中,我会很乐意接受该建议-但这并不能解决我的问题)

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. 请记住,我们实际上并没有中止请求,只是忽略了它的结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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