Обработка ошибок с помощью transformErrorResponse

transformErrorResponse в RTK Query используется для нормализации ошибок, приходящих из baseQuery, и преобразования их в единый формат, удобный для дальнейшей обработки внутри endpoint’ов, reducers и UI-логики. Это механизм, который позволяет отделить транспортный уровень ошибок (HTTP, network, fetchBaseQuery) от доменной модели ошибок приложения.

При выполнении запроса через RTK Query ошибка может приходить в разных формах:

  • HTTP-ошибка (например, 400, 401, 500)
  • Ошибка сети (network error)
  • Ошибка парсинга ответа
  • Кастомная ошибка, возвращённая backend-логикой

Без нормализации каждый endpoint вынужден работать с разнородными структурами, что усложняет обработку. transformErrorResponse решает эту проблему на уровне API-слайса.

Функция трансформации подключается в createApi и применяется ко всем ошибкам, возвращаемым конкретным endpoint’ом.

Сигнатура и базовое поведение

transformErrorResponse принимает три аргумента:

  • response — исходный ответ ошибки от baseQuery
  • meta — метаданные запроса (headers, status и т.д.)
  • arg — аргументы запроса (payload, параметры)

Возвращаемое значение становится error-объектом внутри RTK Query.

Базовый пример:

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({
    baseUrl: '/api',
  }),
  endpoints: (builder) => ({
    getUser: builder.query({
      query: (id) => `user/${id}`,
      transformErrorResponse: (response, meta, arg) => {
        return {
          status: response?.status,
          message: response?.data?.message || 'Ошибка загрузки пользователя',
        };
      },
    }),
  }),
});

В результате error в useGetUserQuery будет иметь унифицированную структуру.

Формат входных данных ошибки

RTK Query передаёт в transformErrorResponse разные структуры в зависимости от baseQuery:

fetchBaseQuery

Типичный формат:

{
  status: number,
  data: any
}

или при сетевой ошибке:

{
  error: string
}

meta может содержать:

  • request
  • response
  • timing

arg содержит исходные параметры запроса.

Нормализация ошибок к единому формату

Основная задача transformErrorResponse — привести разные типы ошибок к единой схеме:

{
  code: string,
  message: string,
  details?: any
}

Пример централизованной нормализации:

transformErrorResponse: (response, meta) => {
  if ('status' in response) {
    return {
      code: String(response.status),
      message: response.data?.message || 'Server error',
      details: response.data,
    };
  }

  return {
    code: 'NETWORK_ERROR',
    message: response.error || 'Network failure',
  };
}

Такой подход устраняет необходимость проверок формата ошибки в компонентах и селекторах.

Работа с HTTP статусами

transformErrorResponse часто используется для маппинга HTTP статусов в бизнес-ошибки:

transformErrorResponse: (response) => {
  switch (response.status) {
    case 400:
      return {
        code: 'BAD_REQUEST',
        message: 'Некорректный запрос',
        fields: response.data?.fields,
      };

    case 401:
      return {
        code: 'UNAUTHORIZED',
        message: 'Требуется авторизация',
      };

    case 403:
      return {
        code: 'FORBIDDEN',
        message: 'Доступ запрещён',
      };

    case 500:
      return {
        code: 'SERVER_ERROR',
        message: 'Ошибка сервера',
      };

    default:
      return {
        code: 'UNKNOWN_ERROR',
        message: 'Неизвестная ошибка',
      };
  }
};

Такой слой абстракции упрощает интеграцию с UI-логикой, где вместо HTTP-статусов используются доменные коды.

Интеграция с TypeScript типизацией

RTK Query позволяет типизировать transformErrorResponse для строгого контроля структуры ошибок.

type ApiError = {
  code: string;
  message: string;
  details?: unknown;
};

type RawError = {
  status: number;
  data?: any;
};

transformErrorResponse: (
  response: RawError,
  meta: any,
  arg: number
): ApiError => {
  return {
    code: String(response.status),
    message: response.data?.message ?? 'Error',
    details: response.data,
  };
};

Типизация особенно важна при использовании error в RTK Query hooks, где дальнейшая обработка зависит от структуры объекта.

Взаимодействие с baseQuery

transformErrorResponse работает поверх baseQuery, не заменяя его поведение. Это означает, что:

  • baseQuery выполняет запрос
  • при ошибке возвращает raw error
  • transformErrorResponse преобразует результат

Пример с кастомным baseQuery:

const baseQuery = fetchBaseQuery({
  baseUrl: '/api',
});

const baseQueryWithAuth = async (args, api, extraOptions) => {
  const result = await baseQuery(args, api, extraOptions);

  if (result.error && result.error.status === 401) {
    // логика refresh token
  }

  return result;
};

transformErrorResponse применяется после этого слоя, что позволяет разделять ответственность.

Использование meta для расширенной диагностики

meta позволяет учитывать технические детали:

transformErrorResponse: (response, meta) => {
  return {
    code: String(response.status || 'NETWORK_ERROR'),
    message: response.data?.message || 'Request failed',
    url: meta?.request?.url,
    method: meta?.request?.method,
    time: meta?.response?.headers?.get('date'),
  };
};

Это используется для логирования, мониторинга и трассировки ошибок.

Поведение при различных типах baseQuery

fetchBaseQuery

Наиболее предсказуемый сценарий. response содержит status и data.

кастомный baseQuery

Если baseQuery возвращает собственный формат:

return {
  error: {
    customCode: 'E_CUSTOM',
    payload: {},
  }
};

transformErrorResponse должен учитывать этот формат явно:

transformErrorResponse: (response) => {
  if (response.customCode) {
    return {
      code: response.customCode,
      message: 'Custom error',
    };
  }

  return {
    code: 'UNKNOWN',
    message: 'Fallback error',
  };
};

Обработка сетевых ошибок

Сетевые ошибки не содержат status и data:

transformErrorResponse: (response) => {
  if (response?.error) {
    return {
      code: 'NETWORK_ERROR',
      message: response.error,
    };
  }

  return {
    code: 'UNKNOWN_ERROR',
    message: 'Unexpected failure',
  };
};

Особенность заключается в отсутствии HTTP-уровня, что требует отдельной ветки обработки.

Централизация логики ошибок

При росте приложения transformErrorResponse становится точкой консолидации:

  • логирование ошибок
  • преобразование backend-формата
  • добавление correlation id
  • нормализация сообщений

Пример централизованной функции:

const normalizeError = (response, meta) => {
  const status = response?.status;

  if (!status) {
    return {
      code: 'NETWORK_ERROR',
      message: response?.error || 'No connection',
    };
  }

  const base = {
    code: `HTTP_${status}`,
    url: meta?.request?.url,
  };

  return {
    ...base,
    message: response.data?.message || 'Request error',
  };
};

transformErrorResponse: normalizeError;

Влияние на useQuery и useMutation

После применения transformErrorResponse error в хуках RTK Query всегда имеет единый формат:

const { error } = useGetUserQuery(1);

if (error.code === 'UNAUTHORIZED') {
  // обработка
}

Без transformErrorResponse структура error зависела бы от реализации backend и baseQuery.

Ограничения и особенности

  • transformErrorResponse не влияет на успешные ответы
  • не изменяет cache key или поведение query
  • применяется только к error branch
  • выполняется синхронно

Также важно учитывать, что чрезмерная логика внутри transformErrorResponse усложняет отладку и делает слой API менее прозрачным.

Практическая архитектурная роль

transformErrorResponse выполняет функцию адаптера между:

  • транспортным уровнем (HTTP / fetch)
  • прикладным уровнем (Redux state)
  • доменным уровнем (business errors)

В зрелых архитектурах он становится частью anti-corruption layer между backend и frontend логикой, обеспечивая стабильность контрактов ошибок независимо от изменений серверной реализации.