如何在 React / TypeScript 中正确取消异步操作?

问题描述 投票:0回答:1

我用它来检查我的服务器是否还活着,每 10 秒轮询一次,最多 10 次,看看它是否还活着。

const wait = (ms: number) => new Promise((res) => setTimeout(res, ms));

export async function checkRemote(
  { maxAttempts = 10 }: { maxAttempts: number } = { maxAttempts: 10 }
) {
  let i = 1;
  while (true) {
    const res = await fetch(`/check`);
    if (res.status >= 400) {
      if (i === maxAttempts) {
        throw new Error("Check failed");
      } else {
        i++;
        await wait(10000);
        continue;
      }
    } else {
      const json = await res.json();
      return json;
    }
  }
}

如何在 React.js 的上下文中正确“取消”此轮询?

问题是,我的

checkRemote
函数将在我的 React 组件卸载后继续执行...如果它在调用仍在进行时卸载。

首先,对

checkRemote
的调用必须在 useEffect
 钩子中进行,
不是作为按钮单击的结果?或者我们可以让它在按钮单击时发生(这在我看来是理想的),但如果他们卸载当前组件,仍然可以取消它?

useEffect(() => { const promise = checkRemote({ maxAttempts: 10 }) .then(res => { setStatus('ready') }).catch(e => { setStatus('unvailable') }) return () => { // on unmount, do something like this? promise.abort() } })
或者也许单击按钮:

const [checkPromise, setCheckPromise] = useState<Promise>() const handleClick = () => { const promise = checkRemote({ maxAttempts: 10 }) .then((res) => { setStatus("ready"); }) .catch((e) => { setStatus("unvailable"); }); setCheckPromise(promise) } useEffect(() => { return () => checkPromise.abort() }, [checkPromise]) return <button onClick={handleClick}>Click me</button>
我如何构建它以将“承诺中止处理程序”传递到我的嵌套函数中?像

AbortController

......
之类的东西

const wait = (ms: number, { controller }) => { return new Promise((res) => { let timer = setTimeout(res, ms) controller.on('abort', () => { clearTimeout(timer) // to respond or not respond then? res() }) }) } export async function checkRemote( { maxAttempts = 10, controller }: { maxAttempts: number } = { maxAttempts: 10, } ) { let i = 1; while (!controller.aborted) { const res = await fetch(`/check`, { signal: controller }); if (res.status >= 400) { if (i === maxAttempts) { throw new Error("Check failed"); } else { i++; // somehow stop the timer early if we abort await wait(10000, { controller }); continue; } } else { const json = await res.json(); return json; } } }
如果我陷入这个兔子洞,你有什么建议?如何构建系统以“正确”中止每个功能?我猜我可能会使用自定义事件发射器来执行此操作,这样我就可以轻松地在整个过程中获得

controller.on('abort')

我不想发生的是我的 10 秒 * 10 次尝试 = ~2 分钟检查器在组件卸载时继续检查,这将是痛苦和混乱的。我希望它基本上取消卸载时的所有内容。

注意:一般问题是如何设置异步中止,而不是

useContext

或其他
针对此特定用例

javascript reactjs asynchronous cancellation abort
1个回答
0
投票
除了使用简单的 AbortController 和使用

throwIfAborted

 方法和/或 
abort
 事件之外,您还可以使用任何提供自己的可取消/可中止 Promise 的库。

以下不是推荐的方法,因为我自己创建了这个库并且它处于测试阶段,所以这只是一个建议: (

现场游乐场)

import { AxiosPromise } from 'axios-promise'; const wait = AxiosPromise.delay; const cancelableFetch = (url, opt) => new AxiosPromise((resolve, _, { signal }) => { resolve(fetch(url, {...opt, signal})); }); const checkRemote = AxiosPromise.promisify(function* (url, { maxAttempts = 10 } = {}) { let i = 1; for (;;) { try { console.log(`request [${i}]`); const res = yield cancelableFetch(url); const simulateFailure = i < 3; if (res.status < 400 && !simulateFailure) { return yield res.json(); } } finally { if (i++ === maxAttempts) { throw new Error('Check failed'); } } yield wait(5000); } }); // ------------ test env ---------------- const runButton = document.querySelector('#run'); const cancelButton = document.querySelector('#cancel'); let promise; cancelButton.onclick = () => { promise?.cancel(); }; runButton.onclick = () => { promise?.cancel(new Error('restart')); promise = checkRemote(`https://dummyjson.com/products/1?delay=1000`).then( (json) => console.log(`Done:`, json), (err) => console.log(`Fail: ${err}`) ); };
    
© www.soinside.com 2019 - 2024. All rights reserved.