在React and TypeScript代码中如何取消fetch请求| 译

1,098 阅读3分钟

这篇文章将介绍在 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 变量用来存储请求数据时的状态('请求中'/'请求已完成'/'请求已取消'/).注意,当状态处于'请求中'时,界面会渲染一个 ”取消“ 按钮

cancel-button

当点击”取消“ 按钮后,我们就可以取消 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 方法。 它还包含一个可以传递给fetchsignal属性。 当调用 AbortController.abort 时,fetch请求就会被取消。

让我们在 getCharacterfetch请求中使用 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 方法的 PromisegetCharacter 中被返回,以便我们在业务代码中可以使用它来取消请求。

保存代码查看界面效果,发现有一个类型错误:

// 💥 - Property 'cancel' does not exist on type 'Promise<unknown>'
promise.cancel = () => controller.abort();

让我们为Promisecancel 方法创建一个类型

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 构建系列

  1. 企业级前端开发规范如何搭建 🛠
  2. 「React Build」之集成 Webpack5/React17
  3. 「React Build」之集成 CSS/Less/Sass/Antd
  4. 「React Build」之集成图片/字体
  5. 「React Build」之集成 Redux/Typescript
  6. 「React Build」之使用 Redux-thunk 实现 Redux 异步操作
  7. 「React Build」之集成 React-Router/Antd Menu 实现路由权限