简体   繁体   English

检查 React 功能组件中的 promise 竞争条件

[英]check promise race condition in React functional component

I have an INPUT NUMBER box that drives a promise.我有一个驱动 promise 的 INPUT NUMBER 框。 The promise result is shown in a nearby DIV. promise 结果显示在附近的 DIV 中。 The user can click very fast to drive the number up and down, and in combination with promises that resolve in different times, this can create race conditions.用户可以非常快速地点击来推动数字的上升和下降,并且结合在不同时间解决的承诺,这可以创造竞争条件。

I would like to keep the DIV always consistent with the INPUT, that is, the DIV should either show nothing ("wait...") if the promise has not resolved yet, or the promise result for the value in the INPUT box.我想让 DIV 始终与 INPUT 保持一致,也就是说,如果 promise 尚未解决,或者 promise 结果为 INPUT 框中的值,则 DIV 应该不显示任何内容(“等待......”)。

To illustrate this use case I wrote this promise utility为了说明这个用例,我编写了这个 promise 实用程序

function getPromise(i){
     return new Promise(resolve=>setTimeout(resolve.bind(null,i), 3000-i*300));
}

That is, the promise will always resolve slower for argument 1 than for 2, and argument 2 resolves slower than 3 etc.也就是说,promise 对参数 1 的解析总是比对 2 慢,而对参数 2 的解析比对 3 慢,以此类推。

In a React class component (or a Vue stateful component) the goal of keeping the INPUT and the DIV consistent can be achieved as follows:在 React class 组件(或 Vue 有状态组件)中,保持 INPUT 和 DIV 一致的目标可以实现如下:

class Test1 extends React.Component{
  constructor(){
     super();
     this.state={val:1, promiseResult:null};
     this.startPromise(1);
  }
  startPromise(x){
     this.setState({val:x, promiseResult:null});
     getPromise(x).then(data=> x===this.state.val && this.setState({promiseResult:data}));
  }
  render(){
      return React.createElement("div",{},
               React.createElement("input", {
                   type:"number" , min:1, max:10, value:this.state.val,
                   onChange:e=>this.startPromise(e.target.value)
               }),
               React.createElement("div", {}, this.state.promiseResult || "wait...")
              );
      }
}

The crux is the check x===this.state.val && that is, I check whether the promise parameter this.state.val is still the same as the one at the time of promise start. The crux is the check x===this.state.val && that is, I check whether the promise parameter this.state.val is still the same as the one at the time of promise start. It could have been changed by rapid user clicks.它可以通过快速的用户点击来改变。

Question is there any way to achieve this with React functional components?问题有没有办法使用 React 功能组件来实现这一点? In the following implementation, it would not be possible for the effect (or any other callback, such as an event listener) to access the current component state because it would do that through a closure, which will always return the val value from the first component render, which is undefined在以下实现中,效果(或任何其他回调,例如事件侦听器)不可能访问当前组件 state 因为它会通过闭包来做到这一点,该闭包将始终从第一个返回val值组件渲染, undefined

function Test(){
   const [val, setValue]=React.useState(1);
   const [promiseResult, setPromiseResult]=React.useState();
   React.useEffect(function(){
      setPromiseResult(null);
      getPromise(val).then(x=> /* cannot check the current component state here! */
                               setPromiseResult(x));
   },[val]);

   return React.createElement("div",{},
               React.createElement("input", {
                     type:"number" , min:1, max:10, value:val, 
                     onChange:e=>setValue(e.target.value)
               }),
               React.createElement("div", {}, promiseResult ||"wait...")
          );
 }

You can use the fact that effects return a cleanup function to make sure your view is in sync.您可以使用效果返回清理 function 的事实来确保您的视图是同步的。

import { useEffect, useState } from "react";
import "./styles.css";

const fakeResults = (s) =>
  new Promise((resolve) => setTimeout(() => resolve("results for " + s), 1000));

export default function App() {
  const [query, setQuery] = useState("");
  const [loading, setLoading] = useState(false);
  const [results, setResults] = useState("");

  useEffect(() => {
    let cancelled = false;
    const effect = async () => {
      if (query) {
        setLoading(true);
        const results = await fakeResults(query);
        if (!cancelled) {
          setLoading(false);
          setResults(results);
        }
      }
    };

    effect();

    return () => {
      cancelled = true;
    };
  }, [query]);

  return (
    <div className="App">
      <input
        placeholder="Search ..."
        onChange={(e) => setQuery(e.target.value)}
      />
      <div>
        <div>results</div>
        {loading && <div>loading...</div>}
        {!loading && results}
      </div>
    </div>
  );
}

编辑love-leaf-m9w9e

The solution as suggested by thedude is to use the cleanup code of the effect thedude建议的解决方案是使用效果的清理代码

    React.useEffect(()=>{
        setPromiseResult(null);
        let cancelled=false;
        getPromise(val).then(data=> !cancelled && setPromiseResult(data));
        return ()=> cancelled=true; 
     },[val]);

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

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