react-query手把手教程⑤-处理错误

1,492 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

处理错误

写在前面

由于国内较少有比较系统的react-query教程,因此笔者结合官方文档以及官方课程的内容,希望写一个较为全面的教程。本文将以各种例子作为切入点,尽可能通俗易懂地讲解相关知识点。如果有错误,还请大家在评论区指出,笔者会尽快改正。

目录

对于react-query来说,任何Promisereject(),或者你在查询函数中抛出的错误,都会被认为是请求出现了错误。

因此,可以基于fetch封装下面的代码来处理HTTP Code为非200的报错:

async function fetchWithError(url, options) {
  const response = await fetch(url, options);

  let errorMessage = "";
  if (response.status !== 200) {
    errorMessage += `Request failed with status ${response.status}. `
  }
  const body = await response.json();
  if (body.error) {
    errorMessage += body.error;
  }

  if (errorMessage) {
    throw new Error(errorMessage);
  }

  return body;
}

同样也可以使用axios这类的三方库,让这类库来帮你自动处理这些错误。

当错误出现时,react-query默认会帮开发者做一些操作,如果不需要这些操作,也可以在配置项中关闭它。

错误重试

如果在第一次加载时未能正确获取到数据,react-query将会立即报错,不会进行重试。

但是在重新获取数据时,由于第一次请求成功的经验,react-query默认后端是可靠的,它可能只是临时出现了错误,通过重试,可能会在之后的请求中,获取到数据。

因此当一个查询无法重新获取时,将会基于指数退避算法的时间间隔,尝试请求3次。 从1s的延迟起步,到30s的最大延迟。下面是默认重试策略的相关配置:

const exampleQuery = useQuery(
  "example",
  fetchExample,
  {
    retry: 3,
    retryDelay: attempt => {
      return Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)
    }
  }
)

PS: 如果只配置了retry的次数,那么retry的时间间隔,将会默认采用指数退避算法。这种算法的好处是前端不会发送太多的尝试请求。你Google一下会发现在后端的一些请求的重试中,也会使用该算法。

如果你认为指数退避算法的时间间隔不合适,希望设置一个固定值(例如5s),同样可以进行配置,如下面例子

const exampleQuery = useQuery(
  "example",
  fetchExample,
  {
    retry: 3,
    retryDelay: 5000
  }
)

你可以通过isError属性来判断当前的请求是否有错误,并针对性的进行处理。

同时你也可以使用React内置的错误边界来处理错误。

错误边界

如果你不了解错误边界,你可以跳转到react官方文档了解具体的内容。

在下面的例子中,我们将会使用react-error-boundary这个包,来减少相关错误边界的配置。

你需要在配置项中,配置useErrorBoundarytrue,之后错误边界就能捕获到相关的报错。

import * as React from 'react';
import './style.css';
import { ErrorBoundary } from 'react-error-boundary';
import { useQuery } from 'react-query';

const fetchWithError = async (url, options = {}) => {
  const response = await fetch(url, options);

  let errorMessage = '';
  if (response.status !== 200) {
    errorMessage += `请求错误状态码为: ${response.status}. `;
  }

  const body = await response.json();
  if (body.message) {
    errorMessage += body.message;
  }

  console.log(errorMessage);

  if (errorMessage) {
    throw new Error(errorMessage);
  }

  return body;
};

const Repos = () => {
  const reposQuery = useQuery(
    ['users'],
    () => fetchWithError('https://api.github.com/users/facebook/repos'),
    {
      useErrorBoundary: true,
      onError: (error) => console.log(error, 'onError'),
    }
  );

  return <div>{JSON.stringify(reposQuery.data)}</div>;
};

const Gists = () => {
  const gistsQuery = useQuery(
    ['gists'],
    () => fetchWithError('https://api.github.com/users/facebook/gists'),
    {
      useErrorBoundary: true,
      onError: (error) => console.log(error, 'onError'),
    }
  );

  return <div>{JSON.stringify(gistsQuery.data)}</div>;
};

function QueryError({ error }) {
  return (
    <div>
      <h1>出现错误</h1>
      <p>{error.message}</p>
    </div>
  );
}

export default function App() {
  return (
    <ErrorBoundary FallbackComponent={QueryError}>
      <Repos />
      <Gists />
    </ErrorBoundary>
  );
}

上面的例子正常工作时,如下图所示: 错误提示

上面的代码你可以去在线演示里查看

请注意:

上面的例子中,在线演示笔者在测试的过程中有时无法正常显示错误边界,取而代之的会是显示stackblitz的报错层。你需要使用create-react-app在本地创建一个例子来尝试上面的例子。当你请求接口次数过多时,github会返回403报错,并告诉你你的调用超出上限。此时你就能看到错误边界开始工作了

错误回调

在react-query中,你可以在配置中设置onError属性,来获取错误的回调。包含了error的数据。

你可以在回调中,调用消息弹窗来提示报错信息,如果你使用ant-design组件库,你可以这么写:

  const reposQuery = useQuery(
    ['users'],
    () => fetchWithError('https://api.github.com/users/facebook/repos'),
    {
      onError: (error) => message.error({content: error.message}),
    }
  );

针对重新获取数据失败处理的方式

下面将模拟一个情景:

在第一次请求数据时,查询函数返回成功。在页面失焦后,重新聚焦,触发第二次请求时,查询函数返回失败

你可以查看在线例子,来实际的体验一下变化过程:

import * as React from 'react';
import { useQuery } from 'react-query';

let count = 0;

export default function App() {
  const mockQuery = useQuery(
    ['mock'],
    async () => {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      if (count === 0) {
        count++;
        return '成功';
      } else {
        throw new Error('失败');
      }
    },
    { retry: false }
  );

  return (
    <div>
      {mockQuery.isError ? (
        <div className="error-message">{mockQuery.error.message}</div>
      ) : null}
      {mockQuery.isLoading ? '首次加载中...' : mockQuery.data}
    </div>
  );
}

我们将会看到以下的步骤:

  1. 页面显示:首次加载中…
  2. 过了1s后,页面显示:成功
  3. 用户操作页面失焦后再聚焦
  4. 过了1s后,页面显示失败 成功

上面的例子,说明了几个问题:

  • 在首次获取数据成功后,即使后面获取数据失败,缓存内依旧会存有之前请求的数据。
  • 你可以根据不同的情况来指定你的显示策略:
    • 当重新获取数据失败后,你可以像上面的例子一样即显示缓存数据,又显示报错
    • 也可以判断isError字段为true时,只显示报错信息,隐藏缓存数据。