错误处理与重试策略

TanStack Query 的错误处理体系:单查询级、全局级、Error Boundary 集成,以及 retry / retryDelay 的配置策略。

#type / howto #status / evergreen #tech / dev / frontend #resource / react

[!info] related notes

错误处理与重试策略

错误处理层级

queryFn 抛出异常

retry 层(自动重试 N 次)

单查询 onError 回调

全局 QueryClient onError

throwOnError → Error Boundary

单查询级错误处理

基本用法

const { data, error, isError } = useQuery({
  queryKey: ['user', id],
  queryFn: () => fetchUser(id),
});

if (isError) {
  return <ErrorMessage message={error.message} />;
}

onError 回调

useQuery({
  queryKey: ['user', id],
  queryFn: () => fetchUser(id),
  onError: (error) => {
    // v4 支持,v5 已移除单查询 onError
    toast.error(`加载失败: ${error.message}`);
  },
});

v5 变化:移除了单查询的 onError / onSuccess / onSettled,改用全局配置或 useEffect

const { data, error, isError } = useQuery({
  queryKey: ['user', id],
  queryFn: () => fetchUser(id),
});

useEffect(() => {
  if (isError) {
    toast.error(`加载失败: ${error.message}`);
  }
}, [isError, error]);

全局错误处理

QueryClient 默认配置

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 3,
      retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30_000),
    },
  },
});

全局 onError

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      onError: (error) => {
        // v4:所有 query 失败都会走这里
        if (error instanceof Error) {
          toast.error(error.message);
        }
      },
    },
    mutations: {
      onError: (error) => {
        toast.error(`操作失败: ${error.message}`);
      },
    },
  },
});

v5 推荐方式:全局事件监听

// v5 用 QueryCache 的 onError
const queryCache = new QueryCache({
  onError: (error, query) => {
    // 可以按 queryKey 区分处理
    if (query.queryKey[0] === 'user') {
      toast.error('用户数据加载失败');
    } else {
      toast.error('请求失败');
    }

    // 401 → 跳登录
    if (error.status === 401) {
      authStore.logout();
    }
  },
});

const mutationCache = new MutationCache({
  onError: (error) => {
    toast.error(`操作失败: ${error.message}`);
  },
});

const queryClient = new QueryClient({ queryCache, mutationCache });

重试策略

默认行为

// 默认:重试 3 次,指数退避
retry: 3
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30_000)
// 第 1 次重试:1 秒
// 第 2 次重试:2 秒
// 第 3 次重试:4 秒

按错误类型决定是否重试

useQuery({
  queryKey: ['user', id],
  queryFn: () => fetchUser(id),
  retry: (failureCount, error) => {
    // 4xx 客户端错误不重试
    if (error.status >= 400 && error.status < 500) {
      return false;
    }
    // 最多重试 3 次
    return failureCount < 3;
  },
});

全局配置

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: (failureCount, error) => {
        // 401/403/404 不重试
        if ([401, 403, 404].includes(error.status)) return false;
        return failureCount < 3;
      },
      retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30_000),
    },
  },
});

禁用重试

// 某些查询不需要重试
useQuery({
  queryKey: ['realtime'],
  queryFn: fetchRealtimeData,
  retry: false,
});

throwOnError 与 Error Boundary

集成 React Error Boundary

import { ErrorBoundary } from 'react-error-boundary';

function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <UserProfile />
    </ErrorBoundary>
  );
}

function UserProfile() {
  const { data } = useQuery({
    queryKey: ['user', id],
    queryFn: () => fetchUser(id),
    throwOnError: true,  // 错误会向上抛给 Error Boundary
  });

  return <div>{data.name}</div>;
}

条件 throwOnError

useQuery({
  queryKey: ['user', id],
  queryFn: () => fetchUser(id),
  throwOnError: (error) => {
    // 只有严重错误才抛给 Error Boundary
    return error.status >= 500;
  },
});

全局配置 throwOnError

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      throwOnError: (error) => error.status >= 500,
    },
  },
});

Mutation 错误处理

const mutation = useMutation({
  mutationFn: createUser,
  onSuccess: () => {
    toast.success('创建成功');
    queryClient.invalidateQueries({ queryKey: ['users'] });
  },
  onError: (error) => {
    // v5 中 mutation 仍支持 onError
    toast.error(`创建失败: ${error.message}`);
  },
});

带重试的 Mutation

const mutation = useMutation({
  mutationFn: createUser,
  retry: 2,  // v5 支持 mutation 重试
  retryDelay: 1000,
});

常见错误

区分网络错误和业务错误

// ❌ 所有错误都当网络错误处理
if (error) return <NetworkError />;

// ✅ 区分处理
if (error) {
  if (error.status === 404) return <NotFound />;
  if (error.status === 403) return <Forbidden />;
  if (error.status >= 500) return <ServerError />;
  return <GenericError message={error.message} />;
}

忽略 AbortError

// ❌ AbortError 被当作真正错误
if (error) toast.error(error.message);

// ✅ 忽略 AbortError
if (error && !(error instanceof DOMException && error.name === 'AbortError')) {
  toast.error(error.message);
}

速查:错误处理策略

场景策略
列表加载失败显示错误提示 + 重试按钮
详情加载失败Error Boundary 或 inline 错误
401 未授权全局拦截 → 跳登录页
404 未找到显示 NotFound 组件
500 服务端错误Error Boundary + 重试
Mutation 失败toast 提示 + 保留表单数据
网络离线[[tanstack-query-network-awareness
创建于 2026/7/3 更新于 2026/7/15