19 KiB
🛡️ Руководство по обработке ошибок
Комплексная система обработки ошибок для Router Lists UI, включающая автоматический retry, user-friendly сообщения и мониторинг состояния сети.
📚 Оглавление
- Компоненты системы
- Типы ошибок
- Автоматический Retry
- Использование в компонентах
- API Reference
- Примеры
- Лучшие практики
🧩 Компоненты системы
1. ErrorBoundary
Глобальный обработчик ошибок React для ловли ошибок рендеринга.
Возможности:
- ✅ Ловит ошибки рендеринга и показывает fallback UI
- ✅ Логирует ошибки в консоль и систему мониторинга
- ✅ Показывает детали ошибки в dev режиме
- ✅ Автоматически очищает кэш при повторяющихся ошибках
- ✅ Кнопки восстановления: "Попробовать снова", "Перезагрузить", "На главную"
Использование:
// Уже встроен в App.jsx, оборачивает все приложение
<ErrorBoundary>
<App />
</ErrorBoundary>
2. NetworkErrorHandler
Компонент для мониторинга состояния сети.
Возможности:
- ✅ Детектирует offline/online события
- ✅ Показывает баннер при потере соединения
- ✅ Уведомляет о восстановлении соединения
- ✅ Интегрируется с глобальной системой уведомлений
Использование:
// Уже встроен в App.jsx
<NetworkErrorHandler />
3. apiErrorHandler.js
Утилиты для классификации и обработки ошибок API.
Функции:
getErrorType(error)- определяет тип ошибкиisRetriableError(error, method)- проверяет возможность retrygetRetryDelay(attemptNumber)- вычисляет задержку для retrygetMaxRetries(errorType, method)- возвращает максимум попытокformatErrorMessage(error)- форматирует user-friendly сообщениеgetErrorDetails(error)- извлекает детали ошибкиgetErrorAction(error)- возвращает рекомендации по устранениюisCriticalError(error)- проверяет критичность ошибкиlogError(error, context)- логирует ошибку
4. useErrorHandler
React хук для обработки ошибок в компонентах.
API:
const {
handleError, // Обработать ошибку
handleSuccess, // Показать успех
handleWarning, // Показать предупреждение
handleInfo, // Показать инфо
withErrorHandler // Обёртка для async функций
} = useErrorHandler();
5. RetryButton
Кнопка для повторной попытки с индикацией загрузки.
Props:
onRetry- async функция для выполненияloading- внешнее состояние загрузкиdisabled- отключить кнопкуclassName- CSS классыshowIcon- показывать иконкуchildren- текст кнопки
🎭 Типы ошибок
Система классифицирует ошибки на следующие типы:
| Тип | Код | Описание | Retry |
|---|---|---|---|
| NETWORK | - | Проблемы с сетью (нет соединения) | ✅ Да (3x) |
| TIMEOUT | ECONNABORTED | Превышено время ожидания | ✅ Да (2x) |
| SERVER | 5xx | Ошибка на сервере | ✅ Да (2x GET) |
| CLIENT | 4xx | Ошибка клиента (общая) | ❌ Нет |
| VALIDATION | 400 | Ошибка валидации данных | ❌ Нет |
| AUTH | 401 | Требуется аутентификация | ❌ Нет |
| PERMISSION | 403 | Нет прав доступа | ❌ Нет |
| NOT_FOUND | 404 | Ресурс не найден | ❌ Нет |
| CONFLICT | 409 | Конфликт данных (ETag) | ❌ Нет |
| RATE_LIMIT | 429 | Превышен лимит запросов | ❌ Нет |
| UNKNOWN | - | Неизвестная ошибка | ❌ Нет |
🔄 Автоматический Retry
Стратегия Retry
GET запросы:
- Network Error: до 3 попыток
- Timeout: до 2 попыток
- Server Error (5xx): до 2 попыток
POST/PUT/PATCH/DELETE:
- Network Error: до 1 попытки
- Timeout: до 1 попытки
- Без retry для 5xx (чтобы не создать дубликаты)
Exponential Backoff
Задержки между попытками растут экспоненциально:
Попытка 1: 300ms (± 20% jitter)
Попытка 2: 600ms (± 20% jitter)
Попытка 3: 1200ms (± 20% jitter)
Jitter (±20%) добавляется для избежания thundering herd problem.
Максимальная задержка
Максимальная задержка ограничена 10 секундами для предотвращения бесконечного ожидания.
💻 Использование в компонентах
Вариант 1: Ручная обработка
import { useState } from 'react';
import { useErrorHandler } from '../hooks/useErrorHandler';
import api from '../lib/api';
function MyComponent() {
const [data, setData] = useState(null);
const { handleError, handleSuccess } = useErrorHandler();
const fetchData = async () => {
try {
const response = await api.get('/domains-new');
setData(response.data);
handleSuccess('Данные загружены');
} catch (error) {
handleError(error, { component: 'MyComponent' });
}
};
return (
<button onClick={fetchData}>
Загрузить данные
</button>
);
}
Вариант 2: Автоматическая обработка
import { useState } from 'react';
import { useErrorHandler } from '../hooks/useErrorHandler';
import api from '../lib/api';
function MyComponent() {
const [data, setData] = useState(null);
const { withErrorHandler } = useErrorHandler();
const fetchData = withErrorHandler(
async () => {
const response = await api.get('/domains-new');
setData(response.data);
},
{
successMessage: 'Данные загружены',
context: { component: 'MyComponent' }
}
);
return (
<button onClick={fetchData}>
Загрузить данные
</button>
);
}
Вариант 3: С RetryButton
import RetryButton from '../components/RetryButton';
import api from '../lib/api';
function MyComponent() {
const saveData = async () => {
await api.post('/domains-new', { domains: [...] });
};
return (
<RetryButton onRetry={saveData}>
Сохранить
</RetryButton>
);
}
Вариант 4: React Query интеграция
import { useQuery } from '@tanstack/react-query';
import { useErrorHandler } from '../hooks/useErrorHandler';
import api from '../lib/api';
function MyComponent() {
const { handleError } = useErrorHandler();
const { data, isLoading, refetch } = useQuery({
queryKey: ['domains'],
queryFn: async () => {
const response = await api.get('/domains-new');
return response.data;
},
onError: (error) => {
handleError(error, { component: 'MyComponent' });
}
});
// React Query автоматически делает retry,
// но мы добавляем user-friendly уведомления
return <div>{/* ... */}</div>;
}
📖 API Reference
useErrorHandler()
interface ErrorHandlerHook {
// Обработать ошибку
handleError: (error: Error, context?: object) => void;
// Показать успех
handleSuccess: (message?: string) => void;
// Показать предупреждение
handleWarning: (message: string, details?: object) => void;
// Показать информацию
handleInfo: (message: string, details?: object) => void;
// Обёртка для async функций
withErrorHandler: (
asyncFn: Function,
options?: {
successMessage?: string;
context?: object;
silent?: boolean;
rethrow?: boolean;
defaultValue?: any;
}
) => Function;
}
getErrorType()
import { getErrorType, ErrorType } from '../lib/api';
const error = new Error('Network error');
const type = getErrorType(error);
if (type === ErrorType.NETWORK) {
console.log('Проблемы с сетью');
}
formatErrorMessage()
import { formatErrorMessage } from '../lib/api';
try {
await api.get('/endpoint');
} catch (error) {
const message = formatErrorMessage(error);
// "Не удалось подключиться к серверу. Проверьте соединение."
alert(message);
}
getErrorAction()
import { getErrorAction } from '../lib/api';
try {
await api.post('/endpoint', data);
} catch (error) {
const action = getErrorAction(error);
// "Проверьте правильность введённых данных."
console.log('Рекомендация:', action);
}
🎯 Примеры
Пример 1: Загрузка данных с обработкой ошибок
import { useState } from 'react';
import { useErrorHandler } from '../hooks/useErrorHandler';
import RetryButton from '../components/RetryButton';
import api from '../lib/api';
function DataLoader() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const { withErrorHandler } = useErrorHandler();
const loadData = withErrorHandler(
async () => {
setLoading(true);
try {
const response = await api.get('/domains-new');
setData(response.data);
} finally {
setLoading(false);
}
},
{
successMessage: 'Данные успешно загружены',
context: { component: 'DataLoader' }
}
);
return (
<div className="card">
<div className="card-body">
{!data ? (
<RetryButton
onRetry={loadData}
loading={loading}
>
Загрузить данные
</RetryButton>
) : (
<div>
<p>Загружено: {data.items.length} записей</p>
<button onClick={loadData}>Обновить</button>
</div>
)}
</div>
</div>
);
}
Пример 2: Форма с валидацией
import { useState } from 'react';
import { useErrorHandler } from '../hooks/useErrorHandler';
import api from '../lib/api';
function DomainForm() {
const [domain, setDomain] = useState('');
const { handleError, handleSuccess } = useErrorHandler();
const handleSubmit = async (e) => {
e.preventDefault();
try {
await api.post('/domains-new', {
domains: [{ domain, community: '65000:100' }]
});
handleSuccess('Домен успешно добавлен');
setDomain('');
} catch (error) {
// Система автоматически покажет user-friendly сообщение
// "Ошибка валидации данных. Проверьте введённые значения."
handleError(error, { form: 'DomainForm', domain });
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={domain}
onChange={(e) => setDomain(e.target.value)}
placeholder="example.com"
/>
<button type="submit">Добавить</button>
</form>
);
}
Пример 3: Обработка критичных ошибок
import { useEffect } from 'react';
import { useErrorHandler } from '../hooks/useErrorHandler';
import { isCriticalError } from '../lib/api';
import api from '../lib/api';
function CriticalDataLoader() {
const { handleError } = useErrorHandler();
useEffect(() => {
const loadCriticalData = async () => {
try {
await api.get('/server-configs');
} catch (error) {
if (isCriticalError(error)) {
// Критичная ошибка - показываем модальное окно
handleError(error);
// Можно также перенаправить на страницу ошибки
// window.location.href = '/error';
}
}
};
loadCriticalData();
}, [handleError]);
return <div>...</div>;
}
✅ Лучшие практики
1. Всегда используйте useErrorHandler
// ✅ Хорошо
import { useErrorHandler } from '../hooks/useErrorHandler';
function MyComponent() {
const { handleError } = useErrorHandler();
// ...
}
// ❌ Плохо
function MyComponent() {
const handleError = (error) => {
alert(error.message); // Не user-friendly
};
}
2. Передавайте контекст в handleError
// ✅ Хорошо
handleError(error, {
component: 'DomainsManager',
action: 'delete',
itemId: domain.id
});
// ❌ Плохо
handleError(error);
3. Используйте withErrorHandler для простых случаев
// ✅ Хорошо - лаконично
const loadData = withErrorHandler(
async () => {
const res = await api.get('/data');
setData(res.data);
},
{ successMessage: 'Загружено' }
);
// ❌ Избыточно
const loadData = async () => {
try {
const res = await api.get('/data');
setData(res.data);
handleSuccess('Загружено');
} catch (error) {
handleError(error);
}
};
4. Не дублируйте обработку ошибок
// ✅ Хорошо - API автоматически показывает уведомление
try {
await api.post('/data', payload);
} catch (error) {
// Ошибка уже показана пользователю
// Здесь только локальная обработка
setLoading(false);
}
// ❌ Плохо - двойное уведомление
try {
await api.post('/data', payload);
} catch (error) {
handleError(error); // Уведомление показано дважды!
}
5. Используйте RetryButton для операций
// ✅ Хорошо
<RetryButton onRetry={saveData}>
Сохранить
</RetryButton>
// ❌ Плохо - ручная реализация retry
<button onClick={async () => {
let retries = 0;
while (retries < 3) {
try {
await saveData();
break;
} catch {
retries++;
}
}
}}>
Сохранить
</button>
6. Логируйте критичные ошибки
// ✅ Хорошо
import { isCriticalError, logError } from '../lib/api';
catch (error) {
if (isCriticalError(error)) {
logError(error, { critical: true, user: currentUser });
}
handleError(error);
}
🔧 Настройка
Изменение таймаута
// frontend/src/lib/api.js
const api = axios.create({
timeout: 30000, // 30 секунд (по умолчанию)
});
Настройка retry параметров
// frontend/src/lib/apiErrorHandler.js
// Изменить базовую задержку
export function getRetryDelay(attemptNumber, baseDelay = 300) {
// Меняем baseDelay для другой стратегии
}
// Изменить количество попыток
export function getMaxRetries(errorType, method = 'GET') {
if (method === 'GET') {
return errorType === ErrorType.NETWORK ? 5 : 3; // Больше попыток
}
// ...
}
Интеграция с Sentry
// frontend/src/lib/apiErrorHandler.js
export function logError(error, context = {}) {
// ...
// Добавить отправку в Sentry
if (window.Sentry && isCriticalError(error)) {
window.Sentry.captureException(error, {
contexts: {
api: getErrorDetails(error),
custom: context
},
level: 'error'
});
}
}
🎓 Дополнительные ресурсы
📝 Changelog
v1.0.0 (2025-10-03)
- ✅ Добавлен ErrorBoundary для React ошибок
- ✅ Добавлен NetworkErrorHandler для offline/online
- ✅ Улучшен retry механизм с exponential backoff
- ✅ Добавлена типизация ошибок (11 типов)
- ✅ User-friendly сообщения вместо технических
- ✅ Хук useErrorHandler для компонентов
- ✅ Компонент RetryButton
- ✅ Интеграция с системой уведомлений
- ✅ Логирование критичных ошибок
- ✅ Увеличен таймаут до 30 секунд
🤝 Поддержка
Если у вас возникли вопросы или проблемы с системой обработки ошибок:
- Проверьте консоль браузера на наличие ошибок
- Проверьте вкладку Network в DevTools
- Убедитесь что backend сервер запущен
- Проверьте настройки CORS
Полезные команды для отладки:
// В консоли браузера
localStorage.clear(); // Очистить кэш
window.notify.clear(); // Очистить уведомления
console.log(window.notify); // Проверить систему уведомлений