Javascript 反应事件AJAX调用竞争条件

Javascript 反应事件AJAX调用竞争条件,javascript,jquery,ajax,reactjs,Javascript,Jquery,Ajax,Reactjs,基本场景 我有一个React textbox控制的组件,其onChange事件最终触发对服务器端API的AJAX调用。此调用的结果可能会更改文本框的值。因此,在AJAX调用的回调中有一个对setState的调用 基本问题 在AJAX调用完成之前对输入进行更改时,我很难找到一种平滑、一致地更新此值的方法。到目前为止,我已经尝试了两种方法。主要区别在于AJAX调用最终是如何发生的 方法1 我的第一次尝试使用立即输入的数据调用setState,最终触发重新渲染和componentdiddupdate。

基本场景

我有一个React textbox控制的组件,其
onChange
事件最终触发对服务器端API的AJAX调用。此调用的结果可能会更改文本框的值。因此,在AJAX调用的回调中有一个对
setState
的调用

基本问题

在AJAX调用完成之前对输入进行更改时,我很难找到一种平滑、一致地更新此值的方法。到目前为止,我已经尝试了两种方法。主要区别在于AJAX调用最终是如何发生的

方法1

我的第一次尝试使用立即输入的数据调用
setState
,最终触发重新渲染和
componentdiddupdate
。后者然后进行AJAX调用,条件是所讨论的状态数据不同

handleChange(event) {
    const inputTextValue = event.target.value;

    setState({ inputText: inputTextValue }); // will trigger componentDidUpdate
}

componentDidUpdate(lastProps, lastState) {
    const inputTextValue = this.state.inputText;

    if (lastState.inputText !== inputTextValue) { // string comparison to prevent infinite loop
        $.ajax({
            url: serviceUrl,
            data: JSON.stringify({ inputText: inputTextValue })
            // set other AJAX options
        }).done((response) => {
            setState({ inputText: response.validatedInputTextValue }); // will also trigger componentDidUpdate
        });
    }
}
这种方法的优点是可以快速更新状态以反映用户的即时输入。但是,如果快速进行两次输入,则会出现如下顺序:

  • 事件处理程序1使用值
    '1'

    • 处理程序1使用值
      '1'
    • 组件从状态更改中重新渲染
    • componentdiddupdate
      从重新渲染触发
    • '1'
      与上一个值不同,因此
    • 使用值
      '1'
  • 当AJAX调用1进行时,事件2处理程序以值
    '12'

    • 处理程序2使用值
      '12'
    • componentdiddupdate
      从重新渲染触发
    • '12'
      不同于
      '1'
      ,因此
    • 使用值
      '12'
  • 当AJAX调用2进行时,AJAX调用1返回值
    '1'

    • AJAX回调1使用值
      '1'
    • componentdiddupdate
      从重新渲染触发
    • '1'
      不同于
      '12'
      ,因此
    • 使用值
      '1'
  • 当AAJX调用3进行时,AJAX调用2返回值
    '12'

  • TL;DR尽管在
    componentdiddupdate
    中进行了最后一次状态检查,但仍会发生无限循环,因为两个重叠的AJAX调用为
    setState
    提供交替值

    方法2

    为了解决这个问题,我的第二种方法简化了系统,并直接从事件处理程序进行AJAX调用:

    handleChange(event) {
        $.ajax({
            url: serviceUrl,
            data: JSON.stringify({ inputText: inputTextValue })
            // set other AJAX options
        }).done((response) => {
            setState({ inputText: response.validatedInputTextValue });
        });
    }
    
    但是,如果我这样做,受控组件值的即时更新将暂停,直到AJAX调用完成并调用
    setState
    。简单稳定,只需设置状态和渲染一次;但在等待AJAX调用时暂停输入是不好的。第一种方法至少有(过度)立即更新的外表

    方法3?

    在等待答案的同时,我将实施以下方法3,它基本上是方法1的增强版本:

    • 将请求ID添加到AJAX调用数据中,该数据在每次调用时递增
    • 在响应中回显请求ID
    • 在回调中,如果当前请求ID大于响应ID,则响应数据已过期
    • 如果响应数据未过期,请调用
      setState
    问题


    我还没有做出反应。我想其他人也遇到过这个用例,但我很难找到解决方案。我想要一种方法来设置状态并立即更新组件的值,即la方法1,并且仍然保持方法2的数据稳定性。方法3似乎很有希望,但有点太复杂了。是否有一种优雅的模式可以实现这一点?

    我最终返回到方法1,但对输入进行去抖动以消除重叠

    特别是,我使用了一个从
    componentdiddupdate
    中的代码重构的方法,该方法实际上进行了AJAX调用:

    constructor(props) {
        super(props);
    
        this.handleChange = this.handleChange.bind(this);
        this.validateInput = this.validateInput.bind(this);
        this.validateInputDebounced = _.debounce(this.validateInput, 100);
    }
    
    handleChange(event) {
        const inputTextValue = event.target.value;
    
        setState({ inputText: inputTextValue }); // will trigger componentDidUpdate
    }
    
    componentDidUpdate(lastProps, lastState) {
        const inputTextValue = this.state.inputText;
    
        if (lastState.inputText !== inputTextValue) { // string comparison to prevent infinite loop
            validateInputDebounced(inputTextValue);
        }
    }
    
    validateInput(newInputTextValue) {
        $.ajax({
            url: serviceUrl,
            data: JSON.stringify({ inputText: newInputTextValue })
            // set other AJAX options
        }).done((response) => {
            setState({ inputText: response.validatedInputTextValue }); // will also trigger componentDidUpdate
        });
    }
    
    这部分是基于此处所做的工作:

    编辑 经进一步检查,这种方法也有不足之处。如果AJAX调用的时间足够长,那么请求可能会再次解决无序问题。我想我会保持去Bounce逻辑以节省网络流量;但是,被接受的解决方案,即取消先前的进行中请求,足以解决这个问题。

    建议的解决方案(#1)有一个很大的警告: 您不能保证第一个请求会在第二个请求之前返回

    为了避免这种情况,您可以采用以下方法之一:

    锁定选择输入: 您选择的组件:

    const Select = props => {
      const {disabled, options} = props;
      return (<select disabled={disabled}>
               { options.map(item => <option value={item}> {item} </option> }
             </select>)
    
    }
    
    
    class LogicalComponent extends React.Component {
    
      constructor(props) {
        this.state = {
          selectDisabled: false;
          options: ['item1', 'item2', 'item3'],
          inputText: ''
        }
      }
    
    handleChange(event) {
        const inputTextValue = event.target.value;
    
        setState({ inputText: inputTextValue }); // will trigger componentDidUpdate
    }
    
    componentDidUpdate(lastProps, lastState) {
        const inputTextValue = this.state.inputText;
    
        if (lastState.inputText !== inputTextValue) { // string comparison to prevent infinite loop
    
            // disabling the select until the request finishes
            this.setState({ selectDisabled: true });
            $.ajax({
                url: serviceUrl,
                data: JSON.stringify({ inputText: inputTextValue })
                // set other AJAX options
            }).done((response) => {
    
                //re-enabling it when done
                setState({ inputText: response.validatedInputTextValue, selectDisabled: false }); // will also trigger componentDidUpdate
             // don't forget to enable it when the request is failed
            }).fail(res => this.setState({selectDisabled: false}));
        }
      }
    
      render() {
         const { selectDisabled, options, inputText } = this.state;
         return <>
                  <Select disabled={selectDisabled} options={options} />
                  <input type="text" value={inputText}/>
                <>
      }
    }
    
    const Select=props=>{
    const{disabled,options}=props;
    返回(
    {options.map(项=>{item}}
    )
    }
    
    您的逻辑组件:

    const Select = props => {
      const {disabled, options} = props;
      return (<select disabled={disabled}>
               { options.map(item => <option value={item}> {item} </option> }
             </select>)
    
    }
    
    
    class LogicalComponent extends React.Component {
    
      constructor(props) {
        this.state = {
          selectDisabled: false;
          options: ['item1', 'item2', 'item3'],
          inputText: ''
        }
      }
    
    handleChange(event) {
        const inputTextValue = event.target.value;
    
        setState({ inputText: inputTextValue }); // will trigger componentDidUpdate
    }
    
    componentDidUpdate(lastProps, lastState) {
        const inputTextValue = this.state.inputText;
    
        if (lastState.inputText !== inputTextValue) { // string comparison to prevent infinite loop
    
            // disabling the select until the request finishes
            this.setState({ selectDisabled: true });
            $.ajax({
                url: serviceUrl,
                data: JSON.stringify({ inputText: inputTextValue })
                // set other AJAX options
            }).done((response) => {
    
                //re-enabling it when done
                setState({ inputText: response.validatedInputTextValue, selectDisabled: false }); // will also trigger componentDidUpdate
             // don't forget to enable it when the request is failed
            }).fail(res => this.setState({selectDisabled: false}));
        }
      }
    
      render() {
         const { selectDisabled, options, inputText } = this.state;
         return <>
                  <Select disabled={selectDisabled} options={options} />
                  <input type="text" value={inputText}/>
                <>
      }
    }
    
    类LogicalComponent扩展了React.Component{
    建造师(道具){
    此.state={
    selectDisabled:false;
    选项:['item1','item2','item3'],
    输入文本:“”
    }
    }
    手变(活动){
    const inputExtValue=event.target.value;
    setState({inputText:inputTextValue});//将触发componentDidUpdate
    }
    componentDidUpdate(lastProps,lastState){
    const inputExtValue=this.state.inputExt;
    if(lastState.inputText!==inputTextValue){//字符串比较以防止无限循环
    //禁用select直到请求完成
    this.setState({selectDisabled:true});
    $.ajax({
    url:serviceUrl,
    数据:JSON.stringify({inputText:inputTextValue})
    //设置其他AJAX选项
    }).完成((响应)=>{
    //我们正在启用它