我有一个 Axios GET
请求,该请求在单击按钮时开始获取 API。此请求需要很长时间(大约一分钟),我想取消它并在再次单击该按钮时启动一个新请求。
此外,如果请求处于待处理状态并且我刷新页面,我还希望能够取消该请求。
我尝试了在 SO 上找到的所有内容,但似乎没有任何效果。我还阅读了有关取消的 Axios 文档:https://github.com/axios/axios#cancellation。
这一切都让我走到了那里:
const CancelToken = axios.CancelToken;
let cancel;
function App() {
useEffect(() => {
async function getPairs() {
try {
if (cancel) {
cancel();
}
const res = await axios.get('http://localhost:3000/binance/pairs?timeframe='+timeframe+'&strat='+strat, {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
});
if (res.status === 200 || res.data.status === 'success') {
setPairs(res.data.pairs);
setReloadConfig(false);
}
}
catch (err) {
if (axios.isCancel(err)) {
console.log("cancelled");
} else {
throw err;
}
}
}
// reloadConfig is a React useState hook that is set to true when clicking a button
if (reloadConfig) {
getPairs();
}
}, [reloadConfig]);
}
export default App;
请注意,reloadConfig
是一个 React useState
钩子,在单击按钮时设置为 true,因此会触发 useEffect
钩子和 Axios 请求。
问题是,如果我多次单击该按钮,则不会取消任何请求,并且每次都会创建一个新请求。我可以看到我的终端中正在处理的所有请求。
如何在单击触发 useEffect
的按钮时取消最后一个待处理请求并创建新请求?
另外,如果我刷新页面,如何取消待处理的请求?
稍微修改代码后,我发现在组件函数声明之前放置 cancelToken
和 cancel
变量让我向前迈进了一步。现在,cancel
不再是 undefined
,我从 cancelled
块收到 catch
消息:
catch (err) {
if (axios.isCancel(err)) {
console.log("cancelled");
}
}
如果我console.log(cancel)
,它返回这个:
ƒ cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
}
所以它告诉我取消成功(如果我理解正确?),但我仍然可以看到所有请求都记录在后端终端中并且仍在处理中。有什么问题吗?
非常感谢
以上显示的解决方案都有效。问题是我的一个误解:当我调用我的 API 以获取结果时,结果实际上是由一个库获取的,该库本身为每个结果获取一个 URI,这意味着对我的 API 的一次调用会多次调用另一个API 通过所述库。
我可以说社区上面展示的解决方案有效,因为我在带有“普通”模拟 API 的沙箱上尝试了它们。我认为在我的情况下发生的情况是,我与 API 一起使用的库调用了 +200 个 URL,所以我猜一个调用被取消了,但另一个 199 个调用没有。
如果有人有取消一切的解决方案,那就太好了,但我认为这更像是一种后端情况,而不是前端情况。
感谢大家的帮助和耐心。
答案 0 :(得分:2)
由于您依赖 useEffect 来触发您的请求,因此您可以使用 useEffect 的清理功能在执行新的 API 请求之前取消您的 API 请求。
function App() {
useEffect(() => {
let CancelToken = axios.CancelToken;
let source = CancelToken.source();
async function getPairs() {
try {
const res = await axios.get('http://localhost:3000/binance/pairs?timeframe='+timeframe+'&strat='+strat, {
cancelToken: source.token
});
if (res.status === 200 || res.data.status === 'success') {
setPairs(res.data.pairs);
setReloadConfig(false);
}
}
catch (err) {
if (axios.isCancel(err)) {
console.log("cancelled");
} else {
throw err;
}
}
}
// reloadConfig is a React useState hook that is set to true when clicking a button
if (reloadConfig) {
getPairs();
}
return () => {
source.cancel('Cancelled due to stale request');
}
}, [reloadConfig]);
}
export default App;
<块引用>
请注意,您应该在 useEffect 中定义您的 cancel
变量
否则它将在下一次渲染时重新初始化为 undefined 如果你
直接在组件内定义。
答案 1 :(得分:0)
也许这不是网络问题,我无法给出准确答案。也许您可以考虑使用队列和广播(websocket)解决方案?
如果你想在刷新页面之前做点什么,请参考window.onbeforeunload
事件。事件回调中执行取消请求的代码。
答案 2 :(得分:0)
认为您应该像这样更改实现以确保避免内存泄漏和处理错误。对 axios 请求使用单独的钩子也将解耦您的组件和请求代码。
您遇到的主要问题是您没有从 source
即 const { token: cancelToken, cancel } = source;
const useFetchPairs = () => {
const [data, setData] = useState({
pairs: null,
isLoading: false,
error: null,
});
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const { cancelToken: token, cancel } = source;
const getPairs = async () => {
setData({ pairs: null, isLoading: false, error: null });
try {
const res = await axios.get(
"http://localhost:3000/binance/pairs?timeframe=" +
timeframe +
"&strat=" +
strat,
{
cancelToken,
}
);
if (res.status === 200 || res.data.status === "success") {
setData({ pairs: res.data.pairs, isLoading: false, error: null });
}
} catch (err) {
if (axios.isCancel(err)) {
console.log("cancelled");
} else {
setData({ pairs: null, isLoading: false, error });
}
}
};
return [data, getPairs, cancel];
};
const MyComponent = () => {
const [{ isLoading, pairs, error }, getPairs, cancel] = useFetchPairs();
useEffect(() => {
return () => {
// cancelling when component unmounts i.e. navigation
cancel();
};
}, []);
const onClick = () => {
if (isLoading) {
// cancelling when button clicked and previous request in progress
cancel();
}
getPairs();
};
return (
<>
{error && <p>{error}</p>}
<div>{pairs}</div>
<button onClick={onClick}>Click me</button>
</>
);
};
答案 3 :(得分:0)
我可以建议另一种使用自定义库 (Working Demo) 的解决方案:
import React, { useState } from "react";
import { useAsyncCallback, E_REASON_UNMOUNTED } from "use-async-effect2";
import { CanceledError } from "c-promise2";
import cpAxios from "cp-axios";
export default function TestComponent(props) {
const [text, setText] = useState("");
const request = useAsyncCallback(
function* () {
this.timeout(props.timeout);
try {
setText("fetching...");
const response = yield cpAxios(props.url);
setText(`Success: ${JSON.stringify(response.data)}`);
// do whatever you need with the response data here
} catch (err) {
CanceledError.rethrow(err, E_REASON_UNMOUNTED); //passthrough
setText(`Failed: ${err}`);
}
},
{ deps: [props.url], cancelPrevious: true }
);
return (
<div className="component">
<div>{text}</div>
<button onClick={request}>Request</button>
<button onClick={request.cancel}>Abort</button>
</div>
);
}