Сетевые запросы нестабильны по своей природе. Даже корректно работающий сервер может временно отвечать ошибками из-за:
Если приложение немедленно переводит запрос в состояние ошибки после первой неудачи, пользователь получает нестабильный интерфейс даже при кратковременных сбоях. TanStack Query решает эту проблему встроенной retry-логикой.
Retry — это механизм автоматического повторного выполнения query после ошибки.
По умолчанию TanStack Query пытается повторить запрос несколько раз перед окончательным переходом в состояние error.
Стандартное поведение библиотеки:
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
Внутри автоматически используется:
retry: 3
Это означает:
Важно понимать:
retry: 3
означает:
1 основной запрос + 3 повторные попытки
Итого — до 4 запросов.
async function fetchUsers() {
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error('Server error')
}
return response.json()
}
function Users() {
const query = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
if (query.isPending) {
return <div>Loading...</div>
}
if (query.isError) {
return <div>Error</div>
}
return (
<ul>
{query.data.map(user => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
)
}
Если сервер временно вернул:
500 Internal Server Error
TanStack Query автоматически повторит запрос.
Пользователь может даже не заметить кратковременный сбой.
Иногда повторные запросы не нужны.
Например:
Retry отключается так:
useQuery({
queryKey: ['profile'],
queryFn: fetchProfile,
retry: false
})
Теперь запрос выполняется только один раз.
Количество попыток можно изменить:
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
retry: 5
})
Теперь TanStack Query выполнит:
1 основной запрос + 5 повторов
Наиболее мощный вариант — использование функции.
Она позволяет принимать решение о повторе динамически.
Retry-функция получает:
retry(failureCount, error)
Где:
failureCount — количество ошибок подряд;error — объект ошибки.useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
retry: (failureCount, error) => {
if (error.status === 404) {
return false
}
return failureCount < 3
}
})
Логика:
404 повтор не выполняется;Ошибка 404 Not Found означает:
Ресурс не существует
Повторный запрос обычно не имеет смысла.
Например:
/api/posts/999999
Если записи нет — повтор не поможет.
Повторные запросы полезны при:
| Код | Причина |
|---|---|
| 500 | Временная ошибка сервера |
| 502 | Bad Gateway |
| 503 | Service Unavailable |
| 504 | Gateway Timeout |
| Network Error | Потеря сети |
| ECONNRESET | Разрыв соединения |
| ETIMEDOUT | Таймаут |
| Код | Причина |
|---|---|
| 400 | Неверный запрос |
| 401 | Нет авторизации |
| 403 | Доступ запрещён |
| 404 | Ресурс отсутствует |
| 422 | Ошибка валидации |
Между повторными запросами TanStack Query делает паузу.
Эта задержка управляется параметром:
retryDelay
TanStack Query использует exponential backoff.
Интервал увеличивается после каждой ошибки.
Примерно так:
1 попытка → 1 секунда
2 попытка → 2 секунды
3 попытка → 4 секунды
Это снижает нагрузку на сервер.
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
retryDelay: 2000
})
Теперь между попытками всегда:
2 секунды
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
retryDelay: attemptIndex => {
return attemptIndex * 1000
}
})
Результат:
1 попытка → 1 секунда
2 попытка → 2 секунды
3 попытка → 3 секунды
Наиболее правильная стратегия retry — постепенное увеличение задержки.
Пример:
retryDelay: attemptIndex => {
return Math.min(1000 * 2 ** attemptIndex, 30000)
}
Механизм:
1 → 1000ms
2 → 2000ms
3 → 4000ms
4 → 8000ms
5 → 16000ms
Максимум:
30000ms
Без увеличения задержки приложение может:
Особенно критично при массовых запросах.
Во время повторных попыток query всё ещё находится в loading-состоянии.
Это означает:
query.isPending === true
Пока retry продолжается:
query.isError === false
Ошибка появляется только после завершения всех попыток.
function Todos() {
const query = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
retry: 3
})
console.log(query.status)
return null
}
Возможная последовательность:
pending
pending
pending
success
или:
pending
pending
pending
error
TanStack Query хранит количество неудачных попыток:
query.failureCount
Пример:
function Posts() {
const query = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
return (
<div>
Failures: {query.failureCount}
</div>
)
}
Последняя ошибка хранится в:
query.failureReason
Пример:
<div>
{query.failureReason?.message}
</div>
TanStack Query умеет определять потерю сети.
Если интернет отсутствует:
Это особенно важно для мобильных приложений.
Retry не связан напрямую с:
refetchOnWindowFocus;refetchOnReconnect;refetchInterval.Но они могут запускать новые запросы, внутри которых снова работает retry-механизм.
Retry можно настроить глобально через QueryClient.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 2
}
}
})
Теперь все query используют:
retry: 2
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retryDelay: 1000
}
}
})
Локальные настройки имеют приоритет.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
retry: false
})
Даже если глобально:
retry: 5
При использовании Axios важно выбрасывать ошибки.
Axios делает это автоматически.
import axios fr om 'axios'
async function fetchUsers() {
const response = await axios.get('/api/users')
return response.data
}
Если сервер вернёт:
500
Axios выбросит exception, и retry сработает.
Fetch API не выбрасывает ошибки для HTTP-кодов.
Нужно делать это вручную.
Неправильно:
async function fetchUsers() {
const response = await fetch('/api/users')
return response.json()
}
Правильно:
async function fetchUsers() {
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error('Request failed')
}
return response.json()
}
Иначе retry не активируется.
Некоторые API возвращают:
429 Too Many Requests
В этом случае retry должен быть осторожным.
Пример:
retry: (failureCount, error) => {
if (error.status === 429) {
return failureCount < 1
}
return failureCount < 3
}
Повторные запросы безопасны не всегда.
GET-запросы обычно безопасны:
GET /posts
Но mutation может вызвать проблемы:
POST /payment
Повтор может создать:
Поэтому retry чаще применяется к query, а не mutation.
Mutation тоже поддерживает retry.
useMutation({
mutationFn: createPost,
retry: 2
})
Но использовать retry для mutation нужно осторожно.
Если query отменяется:
queryClient.cancelQueries()
retry-цепочка тоже прекращается.
Если queryFn поддерживает signal:
async function fetchPosts({ signal }) {
const response = await fetch('/api/posts', {
signal
})
return response.json()
}
TanStack Query сможет корректно прерывать retry.
Часто используется:
retry: (failureCount, error) => {
if (error.status >= 400 && error.status < 500) {
return false
}
return failureCount < 3
},
retryDelay: attempt => {
return Math.min(1000 * 2 ** attempt, 30000)
}
Особенности:
retry: true
Это может привести к бесконечным запросам.
Особенно опасно при падении API.
retryDelay: 0
Создаёт огромную нагрузку.
401 Unauthorized
Повтор не поможет, пока пользователь не авторизуется.
Некоторые запросы:
Retry может многократно ухудшить ситуацию.
const query = useQuery({
queryKey: ['feed'],
queryFn: fetchFeed,
retry: (failureCount, error) => {
if (error.status === 404) {
return false
}
if (error.status === 401) {
return false
}
return failureCount < 3
},
retryDelay: attempt => {
return Math.min(1000 * 2 ** attempt, 10000)
}
})
Поведение: