我用它来检查我的服务器是否还活着,每 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
或其他针对此特定用例。
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}`)
);
};