如何防止反应挂钩中的竞争条件?
How do I prevent a race condition in a react hook?
我已经为 React 写了一个方便的钩子来跟踪一个 promise 是否 运行,是否有错误,以及结果是什么。它是这样使用的:
const MyComponent = (props: IProps) => {
const [promise, setPromise} = useState<Promise | undefined>();
const {
hasRun,
isWaiting,
results,
error
} = useService(promise);
const doSomething = () => {
setPromise(runSomeAsyncCode());
};
return (
<div>...</div>
);
};
它只不过是一组随着 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>();
useEffect(() => {
if (!promise) {
return;
}
(async () => {
if (!hasRun) {
setHasRun(true);
}
setError(undefined);
setIsWaiting(true);
try {
const r = await promise;
setResults(r);
} catch (err) {
setResults(undefined);
setError(err);
} finally {
setIsWaiting(false);
}
})();
}, [promise]);
return {
error,
hasRun,
isWaiting,
results,
};
};
我的问题是如果 promise 在前一个 promise 解决之前更新,那么前一个 promise 的结果或错误仍将返回到组件。例如,启动几个 AJAX 请求,第一个请求在一分钟后失败,而第二个请求在几秒钟内解决,导致最初成功,但一分钟后报告失败。
如果 promise 在此期间发生了变化,我该如何修改挂钩,使其不会因错误或成功而调用 setState?
为什么不跟踪当前的 promise
,如果 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 元素引用。
Essentially, useRef is like a “box” that can hold a mutable value in its .current property. [...] Mutating the .current property doesn’t cause a re-render.
我在这里使用 useRef
的原因是因为我们需要一个可变值来保存最新的 promise
参数而不会导致重新渲染。因为 promiseRef
永远不会改变(只有 .current
会改变),你可以在 **
的行上分配最新的值并在异步函数中访问它,即使在时间过去并且组件有重新渲染。
我已经为 React 写了一个方便的钩子来跟踪一个 promise 是否 运行,是否有错误,以及结果是什么。它是这样使用的:
const MyComponent = (props: IProps) => {
const [promise, setPromise} = useState<Promise | undefined>();
const {
hasRun,
isWaiting,
results,
error
} = useService(promise);
const doSomething = () => {
setPromise(runSomeAsyncCode());
};
return (
<div>...</div>
);
};
它只不过是一组随着 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>();
useEffect(() => {
if (!promise) {
return;
}
(async () => {
if (!hasRun) {
setHasRun(true);
}
setError(undefined);
setIsWaiting(true);
try {
const r = await promise;
setResults(r);
} catch (err) {
setResults(undefined);
setError(err);
} finally {
setIsWaiting(false);
}
})();
}, [promise]);
return {
error,
hasRun,
isWaiting,
results,
};
};
我的问题是如果 promise 在前一个 promise 解决之前更新,那么前一个 promise 的结果或错误仍将返回到组件。例如,启动几个 AJAX 请求,第一个请求在一分钟后失败,而第二个请求在几秒钟内解决,导致最初成功,但一分钟后报告失败。
如果 promise 在此期间发生了变化,我该如何修改挂钩,使其不会因错误或成功而调用 setState?
为什么不跟踪当前的 promise
,如果 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 元素引用。
Essentially, useRef is like a “box” that can hold a mutable value in its .current property. [...] Mutating the .current property doesn’t cause a re-render.
我在这里使用 useRef
的原因是因为我们需要一个可变值来保存最新的 promise
参数而不会导致重新渲染。因为 promiseRef
永远不会改变(只有 .current
会改变),你可以在 **
的行上分配最新的值并在异步函数中访问它,即使在时间过去并且组件有重新渲染。