持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
处理错误
写在前面
由于国内较少有比较系统的react-query教程,因此笔者结合官方文档以及官方课程的内容,希望写一个较为全面的教程。本文将以各种例子作为切入点,尽可能通俗易懂地讲解相关知识点。如果有错误,还请大家在评论区指出,笔者会尽快改正。
目录
- 入门react-query 已于2022-06-04更新
- 深入查询键及查询函数 已于2022-06-08更新
- 并行请求及依赖请求 已于2022-06-19更新
- 查询结果缓存状态与调试工具 已于2022-06-23更新
- 处理错误 已于2022-06-26更新
对于react-query来说,任何Promise
的reject()
,或者你在查询函数中抛出的错误,都会被认为是请求出现了错误。
因此,可以基于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
这个包,来减少相关错误边界的配置。
你需要在配置项中,配置useErrorBoundary
为true
,之后错误边界就能捕获到相关的报错。
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>
);
}
我们将会看到以下的步骤:
- 页面显示:首次加载中…
- 过了1s后,页面显示:成功
- 用户操作页面失焦后再聚焦
- 过了1s后,页面显示失败 成功
上面的例子,说明了几个问题:
- 在首次获取数据成功后,即使后面获取数据失败,缓存内依旧会存有之前请求的数据。
- 你可以根据不同的情况来指定你的显示策略:
- 当重新获取数据失败后,你可以像上面的例子一样即显示缓存数据,又显示报错
- 也可以判断
isError
字段为true
时,只显示报错信息,隐藏缓存数据。