乐趣区

关于javascript:如何取消fetch请求

这篇文章将介绍在 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 办法。它还蕴含一个能够传递给 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 将其捕捉。

参考文档:https://www.carlrippon.com/ca…

退出移动版