如何防止反应钩中出现竞争状况?

时间:2020-06-16 18:24:44

标签: javascript reactjs promise race-condition

我已经为React写了一个方便钩子,用于跟踪一个承诺是否正在运行,是否存在错误以及结果是什么。这样使用:

# desired output:
tensor([  -5.0000, -100.0000,   -1.0000,    0.1000,    7.0000,  100.0000])

只不过是随着承诺开始,运行,成功或失败而更新的一组状态:

const MyComponent = (props: IProps) => {
  const [promise, setPromise} = useState<Promise | undefined>();
  const {
    hasRun,
    isWaiting,
    results,
    error
  } = useService(promise);

  const doSomething = () => {
    setPromise(runSomeAsyncCode());
  };

  return (
    <div>...</div>
  );
};

我的问题是,如果承诺在先前的承诺解决之前更新,那么先前的承诺的结果或错误仍将返回给组件。例如,启动几个AJAX请求,第一个请求在一分钟后失败,但是第二个请求在几秒钟内解决,则导致最初成功,但是一分钟后又报告失败。

如果在此期间更改了承诺,如何修改挂钩,以使其不会调用setState来获取错误或成功?

Codepen:https://codepen.io/whiterook6/pen/gOPwJGq

1 个答案:

答案 0 :(得分:1)

为什么不跟踪当前的promise,并在诺言改变的情况下保全效果?

export const useService = <T>(promise?: Promise<T>) => {
    const [hasRun, setHasRun] = useState<boolean>(false);
    const [isWaiting, setIsWaiting] = useState<boolean>(false);
    const [error, setError] = useState<Error | undefined>(undefined);
    const [results, setResults] = useState<T | undefined>();

    const promiseRef = useRef(promise);
    promiseRef.current = promise; // ** keep ref always up to date

    useEffect(() => {
        if (!promise) {
            return;
        }

        (async () => {
            setHasRun(true);
            setError(undefined);

            setIsWaiting(true);
            try {
                const r = await promise;
                if (promiseRef.current !== promise) {
                    return;
                }
                setResults(r);
                setIsWaiting(false);
            } catch (err) {
                if (promiseRef.current !== promise) {
                    return;
                }
                setResults(undefined);
                setError(err);
                setIsWaiting(false);
            }
        })();

        // you may want to reset states when the promise changes
        return () => {
            setHasRun(false);
            setIsWaiting(false);
            setError(undefined);
            setResults(undefined);
        }
    }, [promise]);

    return {
        error,
        hasRun,
        isWaiting,
        results,
    };
};
正如docs所指出的,

useRef不仅仅用于DOM元素引用。

从本质上讲,useRef就像一个“盒子”,可以在其.current属性中保留一个可变值。 [...]更改.current属性不会导致重新渲染。

我在这里使用useRef的原因是因为我们需要一个可变的值,该值可以容纳最新的promise参数而不会引起重新渲染。由于promiseRef永不更改(仅.current会更改),因此您可以在**的行上分配最新值,并在异步函数中访问它,即使时间已经过去并且组件已经重新呈现。