这篇文章将介绍在 React 和 TypeScript 应用中,如何取消fetch请求
一个 React 组件
以下是一个通过 Web API 获取一些数据并进行渲染的 React 组件
export function App() {
const [status, setStatus] = React.useState<
"loading" | "loaded" | "cancelled"
>("loading");
const [data, setData] = React.useState<Character | undefined>(undefined);
React.useEffect(() => {
getCharacter(1).then((character) => {
setData(character);
setStatus("loaded");
});
}, []);
if (status === "loading") {
return (
<div>
<div>loading ...</div>
<button>Cancel</button>
</div>
);
}
if (status === "cancelled") {
return <div>Cancelled</div>;
}
return <div>{data && <h3>{data.name}</h3>}</div>;
}
网络数据通过 useEffect 中的getCharacter函数获取,然后再使用useState设置到 data中
同时,我们定义一个status 变量用来存储请求数据时的状态('请求中'/'请求已完成'/'请求已取消'/).注意,当状态处于'请求中'时,界面会渲染一个 ”取消“ 按钮
当点击”取消“ 按钮后,我们就可以取消 fetch 请求
接下来,让我们看看getCharacter函数里面有什么
async function getCharacter(id: number) {
const response = await fetch(`https://swapi.dev/api/people/${id}/`);
const data = await response.json();
assertIsCharacter(data);
return data;
}
代码中通过 fetch 调用了一个免费的接口并返回请求到的数据
下面 Character的类型
type Character = {
name: string;
};
接下来,再看看assertIsCharacter函数
function assertIsCharacter(data: any): asserts data is Character {
if (!("name" in data)) {
throw new Error("Not character");
}
}
通过 TypeScript 的 类型断言 将数据类型设置为Character。
使用AbortController来取消 fetch
1AbortController 是 JavaScript 的最新版本中的特性,它是在 fetch 被实现之后出现的。 更好的消息是所有现代浏览器都支持它。
AbortController 包含一个 abort 方法。 它还包含一个可以传递给fetch的signal属性。 当调用 AbortController.abort 时,fetch请求就会被取消。
让我们在 getCharacter 的fetch请求中使用 AbortController 及其signal属性:
function getCharacter(id: number) {
// 获取AbortController实例
const controller = new AbortController();
// 获取 signal属性
const signal = controller.signal;
const promise = new Promise(async (resolve) => {
const response = await fetch(`https://swapi.dev/api/people/${id}/`, {
method: "get",
// 将 signal作为fetch的参数之一
signal,
});
const data = await response.json();
assertIsCharacter(data);
resolve(data);
});
// 设置一个 取消函数
promise.cancel = () => controller.abort();
return promise;
}
我们从getCharacter函数中删除了async关键字,并将现有代码包装在一个新的Promise中。 当请求到数据后,我们使用resolve将数据抛出去。 我们在Promise中添加了一个cancel方法,该方法调用了AbortController.abort。
包含 cancel 方法的 Promise 从 getCharacter 中被返回,以便我们在业务代码中可以使用它来取消请求。
保存代码查看界面效果,发现有一个类型错误:
// 💥 - Property 'cancel' does not exist on type 'Promise<unknown>'
promise.cancel = () => controller.abort();
让我们为Promise的 cancel 方法创建一个类型
interface PromiseWithCancel<T> extends Promise<T> {
cancel: () => void;
}
// 使用 然使用类型断言来解决前面的类型错误
function getCharacter(id: number) {
...
(promise as PromiseWithCancel<Character>).cancel = () => controller.abort();
return promise as PromiseWithCancel<Character>;
}
在 React 组件中使用getCharacter
我们将把getCharacter返回的 promise 存储在一个名为query的状态变量中。
export function App() {
const [status, setStatus] = React.useState<"loading" | "loaded" | "cancelled">("loading");
const [data, setData] = React.useState<Character | undefined>(undefined);
const [query, setQuery] = React.useState<PromiseWithCancel<Character> | undefined>(undefined);
React.useEffect(() => {
const q = getCharacter(1);
setQuery(q);
q.then((character) => {
setData(character);
setStatus("loaded");
});
}, []);
...
现在,当点击取消按钮的时候,我们调用 promise 中的cancle方法
<button
onClick={() => {
query?.cancel();
setStatus("cancelled");
}}
>
Cancel
</button>
点击取消按钮后吗,我们看到发现’Cancelled‘文案被渲染出来了
再通过谷歌开发者工具查看网络请求,发现请求也被取消了
很棒是吧!😊
捕获"取消请求"发生的错误
让我们再看看控制台,当请求被取消后,有错误被抛出了
我们可以使用 try catch 来包裹请求以便捕获错误
const promise = new Promise(async (resolve) => {
try {
const response = await fetch(`https://swapi.dev/api/people/${id}/`, {
method: "get",
signal,
});
const data = await response.json();
assertIsCharacter(data);
resolve(data);
} catch (ex: unknown) {
if (isAbortError(ex)) {
console.log(ex.message);
}
}
});
isAbortError类型的函数如下
function isAbortError(error: any): error is DOMException {
if (error && error.name === "AbortError") {
return true;
}
return false;
}
现在当我们再次点击取消按钮,我们会在控制台收到一条消息提示而不是一个错误
总结
可以将AbortController中的signal属性传递给fetch。 然后可以调用AbortController.abort取消请求。
取消 fetch 会引发一个错误,但可以使用try catch将其捕获。
参考文档: www.carlrippon.com/cancelling-…
最后
你还知道其他中断请求的方式吗?欢迎在评论区留下的你的见解!
觉得有收获的朋友欢迎点赞,关注一波!
往期文章
react 构建系列