Взаимодействие с сервером в RTK Query строится вокруг единого
механизма выполнения запросов через baseQuery, где ошибки
представляют собой структурированные результаты выполнения запроса.
Основное разделение происходит на несколько классов: сетевые ошибки,
HTTP-ошибки, ошибки парсинга ответа и прикладные ошибки, возвращаемые
сервером в теле ответа.
Сетевые ошибки возникают на уровне транспорта: отсутствие соединения,
таймауты, блокировка CORS, сбои DNS. Такие ошибки обычно не содержат
HTTP-статуса и формируются как объект error с полем
status: 'FETCH_ERROR'.
HTTP-ошибки формируются при наличии ответа сервера с кодом вне
диапазона 200–299. В RTK Query они фиксируются как
{ status: number, data: unknown }.
Ошибки формата данных появляются при невозможности корректно
разобрать ответ (например, при несоответствии JSON-формата). В таких
случаях возвращается PARSING_ERROR.
Прикладные серверные ошибки — это бизнес-ошибки, когда запрос
технически успешен, но сервер возвращает структуру вида
{ error: string, code: string } или аналогичную. Такие
ошибки требуют явного извлечения из data.
fetchBaseQuery предоставляет стандартную реализацию
обработки HTTP-запросов, где ошибки автоматически нормализуются в единый
формат.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: '/api'
}),
endpoints: (builder) => ({
getUser: builder.query({
query: (id) => `user/${id}`
})
})
});
При таком подходе ошибка автоматически попадает в error
поля результата запроса и доступна через isError,
error, isLoading.
RTK Query унифицирует ошибки в следующую структуру:
status: HTTP-код, строка 'FETCH_ERROR',
'PARSING_ERROR' или 'CUSTOM_ERROR'data: тело ответа сервера (если доступно)error: текстовое описаниеoriginalStatus: исходный HTTP-статус (в некоторых
случаях)Пример:
{
status: 401,
data: {
message: 'Unauthorized',
code: 'AUTH_EXPIRED'
}
}
Для серверов с нестандартным форматом ответов применяется
transformErrorResponse. Этот механизм позволяет привести
любую серверную ошибку к унифицированной форме.
getUser: builder.query({
query: (id) => `user/${id}`,
transformErrorResponse: (response) => {
return {
status: response.status,
message: response.data?.message || 'Unknown error',
code: response.data?.code
};
}
});
После преобразования error в состоянии запроса содержит
уже нормализованную структуру, удобную для логики приложения.
Мутации и запросы в RTK Query позволяют перехватывать ошибки через
onQueryStarted. Этот механизм используется для побочных
эффектов и контроля состояния при провале запроса.
addUser: builder.mutation({
query: (user) => ({
url: 'user',
method: 'POST',
body: user
}),
async onQueryStarted(arg, { queryFulfilled }) {
try {
await queryFulfilled;
} catch (err) {
console.log('Ошибка запроса:', err.error);
}
}
});
queryFulfilled возвращает промис, который отклоняется
при любой серверной или сетевой ошибке.
Серверные ошибки часто требуют централизованного сопоставления кодов
с действиями. RTK Query позволяет реализовать это через кастомный
baseQuery.
const baseQuery = fetchBaseQuery({
baseUrl: '/api'
});
const baseQueryWithErrorMapping = async (args, api, extraOptions) => {
const result = await baseQuery(args, api, extraOptions);
if (result.error) {
if (result.error.status === 401) {
// обработка авторизации
}
if (result.error.status === 500) {
// логирование серверной ошибки
}
}
return result;
};
Такой слой позволяет реализовать глобальную стратегию обработки ошибок без дублирования в каждом endpoint.
Мутации требуют особого подхода, так как они часто сопровождаются оптимистическими обновлениями. При ошибке необходимо откатить локальное состояние.
updateUser: builder.mutation({
query: (user) => ({
url: `user/${user.id}`,
method: 'PUT',
body: user
}),
async onQueryStarted(user, { dispatch, queryFulfilled }) {
const patch = dispatch(
api.util.updateQueryData('getUser', user.id, (draft) => {
Object.assign(draft, user);
})
);
try {
await queryFulfilled;
} catch {
patch.undo();
}
}
});
Ошибка сервера здесь приводит к восстановлению предыдущего состояния кэша, что предотвращает рассинхронизацию UI и backend.
Ошибки парсинга возникают при некорректном JSON или неожиданной
структуре ответа. RTK Query фиксирует их как
PARSING_ERROR.
baseQuery: fetchBaseQuery({
baseUrl: '/api',
responseHandler: async (response) => {
const text = await response.text();
try {
return JSON.parse(text);
} catch {
throw new Error('Invalid JSON');
}
}
});
Подобная логика позволяет дополнительно контролировать корректность данных до попадания в кэш.
Многие API возвращают ошибки в разных форматах:
{ "error": "message" }
или
{ "message": "error", "errors": [] }
Для унификации используется слой адаптации:
const normalizeError = (data) => {
if (!data) return 'Unknown error';
if (typeof data === 'string') return data;
return data.message || data.error || 'Unknown error';
};
И интеграция в transformErrorResponse:
transformErrorResponse: (response) => ({
status: response.status,
message: normalizeError(response.data)
})
Сетевые ошибки фиксируются без HTTP-статуса и требуют отдельной обработки. В RTK Query они выглядят как:
{
status: 'FETCH_ERROR',
error: 'TypeError: Failed to fetch'
}
Для таких случаев часто применяется проверка:
if (error?.status === 'FETCH_ERROR') {
// отсутствие сети
}
При работе в офлайн-режиме важно учитывать, что RTK Query продолжает хранить кэшированные данные, а повторные запросы автоматически выполняются при восстановлении соединения, если активна подписка на компонент.
Механизм retry позволяет автоматически повторять запросы при временных сбоях сервера.
import { retry } from '@reduxjs/toolkit/query';
const baseQuery = retry(fetchBaseQuery({ baseUrl: '/api' }), {
maxRetries: 3
});
Повторные попытки особенно полезны при обработке 502,
503, 504 ошибок, когда сервер нестабилен.
Многие серверные API используют доменные коды ошибок:
{
"code": "USER_BLOCKED",
"message": "User is blocked"
}
Такие коды удобно обрабатывать через централизованную карту:
const errorHandlers = {
USER_BLOCKED: () => {
// блокировка интерфейса
},
AUTH_EXPIRED: () => {
// очистка сессии
}
};
И вызов при получении ошибки:
if (error?.data?.code) {
errorHandlers[error.data.code]?.();
}
RTK Query использует промисы для выполнения запросов, поэтому ошибки
могут быть перехвачены через unwrap.
try {
await dispatch(api.endpoints.getUser.initiate(id)).unwrap();
} catch (err) {
console.log(err);
}
unwrap переводит результат в чистый промис, где ошибка
пробрасывается через throw, что упрощает обработку в логике
middleware или saga-подобных сценариях.
RTK Query хранит ошибки в Redux store, поэтому они должны быть сериализуемыми. Нежелательно помещать туда:
Рекомендуемый формат — plain object:
{
status: number | string,
message: string,
code?: string
}
При параллельных запросах возможна ситуация, когда ошибка относится к
уже устаревшему запросу. RTK Query использует внутренние идентификаторы
requestId, позволяющие различать состояния.
async onQueryStarted(arg, { requestId, queryFulfilled }) {
try {
await queryFulfilled;
} catch (e) {
// ошибка относится к конкретному requestId
}
}
Это предотвращает влияние устаревших ошибок на актуальное состояние интерфейса.
RTK Query сохраняет ошибки в кэше вместе с данными запроса. Это означает, что повторный рендер компонента может использовать уже сохранённое состояние ошибки без повторного запроса. Такая модель делает поведение предсказуемым: ошибка становится частью состояния данных, а не временным побочным эффектом вызова API.