Files
router-lists-ui/ERROR_HANDLING_FLOW.md

14 KiB
Raw Permalink Blame History

🔄 Схема работы системы обработки ошибок

Поток обработки ошибок

flowchart TD
    Start([Пользователь выполняет действие]) --> API[API Request via axios]
    
    API --> Success{Успех?}
    
    Success -->|Да| Cache[Обновить кэш<br/>GET запросы]
    Cache --> ShowSuccess[Показать успех<br/>если указано]
    ShowSuccess --> End([Завершено])
    
    Success -->|Нет| ErrorType{Тип ошибки?}
    
    ErrorType -->|Network| CheckRetry1[Проверить retry<br/>GET: 3x, POST: 1x]
    ErrorType -->|Timeout| CheckRetry2[Проверить retry<br/>GET: 2x, POST: 1x]
    ErrorType -->|Server 5xx| CheckRetry3[Проверить retry<br/>GET: 2x]
    ErrorType -->|Client 4xx| NoRetry1[Нет retry]
    ErrorType -->|Unknown| NoRetry2[Нет retry]
    
    CheckRetry1 --> CanRetry{Есть попытки?}
    CheckRetry2 --> CanRetry
    CheckRetry3 --> CanRetry
    
    CanRetry -->|Да| Backoff[Exponential Backoff<br/>300ms → 600ms → 1200ms]
    Backoff --> API
    
    CanRetry -->|Нет| LogError[Логировать ошибку<br/>если критичная]
    NoRetry1 --> LogError
    NoRetry2 --> LogError
    
    LogError --> FormatMessage[Форматировать<br/>user-friendly сообщение]
    FormatMessage --> GetAction[Получить рекомендацию<br/>по устранению]
    GetAction --> ShowNotification[Показать уведомление<br/>Error/Warning]
    ShowNotification --> RejectPromise[Отклонить Promise]
    RejectPromise --> ComponentHandler[Обработка в компоненте]
    
    ComponentHandler --> ErrorBoundary{Ошибка<br/>рендеринга?}
    ErrorBoundary -->|Да| ShowErrorPage[Показать страницу<br/>ошибки]
    ErrorBoundary -->|Нет| UserHandler[useErrorHandler]
    
    ShowErrorPage --> Recovery[Кнопки восстановления]
    UserHandler --> HandleLogic[Локальная обработка]
    
    Recovery --> End
    HandleLogic --> End

Структура компонентов

graph TB
    App[App.jsx] --> EB[ErrorBoundary]
    EB --> Router[React Router]
    Router --> QueryClient[React Query]
    QueryClient --> Theme[Theme Provider]
    Theme --> Lang[Language Provider]
    Lang --> Toast[Toast Container]
    Toast --> Notify[Notify Provider]
    Notify --> NEH[Network Error Handler]
    Notify --> Layout[Main Layout]
    
    Layout --> Pages[Page Components]
    Pages --> useEH[useErrorHandler hook]
    Pages --> RB[RetryButton]
    Pages --> API[api.js]
    
    API --> Interceptors[Axios Interceptors]
    Interceptors --> ErrorHandler[apiErrorHandler.js]
    
    ErrorHandler --> Types[Error Types]
    ErrorHandler --> Retry[Retry Logic]
    ErrorHandler --> Format[Message Formatting]
    
    style EB fill:#ff6b6b
    style NEH fill:#51cf66
    style ErrorHandler fill:#ffd43b
    style useEH fill:#339af0

Жизненный цикл запроса с ошибкой

sequenceDiagram
    participant User as Пользователь
    participant Comp as Компонент
    participant Hook as useErrorHandler
    participant API as api.js
    participant Inter as Interceptor
    participant Handler as errorHandler.js
    participant Server as Backend
    participant Notify as Notify System
    
    User->>Comp: Клик на кнопку
    Comp->>Hook: withErrorHandler(fn)
    Hook->>API: api.get('/data')
    API->>Inter: Request Interceptor
    Inter->>Server: HTTP Request
    
    alt Успех
        Server-->>Inter: 200 OK
        Inter-->>API: Response
        API-->>Hook: Data
        Hook-->>Notify: handleSuccess
        Notify-->>User: ✅ Успех
    else Ошибка (первая попытка)
        Server-->>Inter: 500 Error
        Inter->>Handler: getErrorType(error)
        Handler-->>Inter: SERVER
        Inter->>Handler: isRetriableError(error, 'GET')
        Handler-->>Inter: true
        Inter->>Handler: getRetryDelay(0)
        Handler-->>Inter: 300ms
        Inter->>Inter: Ждём 300ms
        Inter->>Server: Retry #1
        
        alt Успех после retry
            Server-->>Inter: 200 OK
            Inter-->>API: Response
            API-->>Hook: Data
            Hook-->>Notify: handleSuccess
            Notify-->>User: ✅ Успех
        else Все попытки исчерпаны
            Server-->>Inter: 500 Error
            Inter->>Handler: formatErrorMessage(error)
            Handler-->>Inter: User-friendly message
            Inter->>Handler: getErrorAction(error)
            Handler-->>Inter: Recommendation
            Inter->>Handler: logError(error)
            Handler->>Handler: Log to console
            Handler-->>Notify: error notification
            Notify-->>User: ❌ Ошибка с деталями
            Inter-->>API: Reject Promise
            API-->>Hook: Error
            Hook->>Comp: Handle locally
        end
    end

Принятие решений о retry

flowchart TD
    Error[Получена ошибка] --> GetType[Определить тип ошибки]
    
    GetType --> IsGET{Метод GET?}
    
    IsGET -->|Да| CheckTypeGET{Тип ошибки}
    CheckTypeGET -->|Network| Retry3[Max 3 попытки]
    CheckTypeGET -->|Timeout| Retry2[Max 2 попытки]
    CheckTypeGET -->|Server 5xx| Retry2b[Max 2 попытки]
    CheckTypeGET -->|Client 4xx| NoRetry[Нет retry]
    
    IsGET -->|Нет| IsSafe{Безопасный<br/>метод?}
    
    IsSafe -->|PUT/DELETE| Limited[Ограниченный retry]
    IsSafe -->|POST/PATCH| Limited
    
    Limited --> CheckTypePOST{Тип ошибки}
    CheckTypePOST -->|Network| Retry1[Max 1 попытка]
    CheckTypePOST -->|Timeout| Retry1b[Max 1 попытка]
    CheckTypePOST -->|Server 5xx| NoRetry2[Нет retry<br/>Риск дубликатов]
    CheckTypePOST -->|Client 4xx| NoRetry3[Нет retry]
    
    Retry3 --> CheckCount{Попытка <br/> max?}
    Retry2 --> CheckCount
    Retry2b --> CheckCount
    Retry1 --> CheckCount
    Retry1b --> CheckCount
    
    CheckCount -->|Да| Backoff[Exponential Backoff<br/>+ Jitter]
    CheckCount -->|Нет| Final[Финальная ошибка]
    
    Backoff --> DoRetry[Повторить запрос]
    DoRetry --> Success{Успех?}
    Success -->|Да| Done[✅ Завершено]
    Success -->|Нет| GetType
    
    NoRetry --> Final
    NoRetry2 --> Final
    NoRetry3 --> Final
    Final --> ShowError[Показать ошибку<br/>пользователю]
    ShowError --> End([Конец])
    Done --> End
    
    style Retry3 fill:#51cf66
    style Retry2 fill:#51cf66
    style Retry2b fill:#51cf66
    style Retry1 fill:#ffd43b
    style Retry1b fill:#ffd43b
    style NoRetry fill:#ff6b6b
    style NoRetry2 fill:#ff6b6b
    style NoRetry3 fill:#ff6b6b

Обработка различных сценариев

Сценарий 1: Сетевая ошибка (Network Error)

🔴 Пользователь загружает список доменов
    ↓
⚡ Backend не отвечает (offline)
    ↓
🔄 Попытка 1: Ждём 300ms → Retry
    ↓
🔄 Попытка 2: Ждём 600ms → Retry
    ↓
🔄 Попытка 3: Ждём 1200ms → Retry
    ↓
❌ Все попытки исчерпаны
    ↓
📢 Показать: "Не удалось подключиться к серверу"
    ↓
💡 Рекомендация: "Проверьте соединение с интернетом"

Сценарий 2: Таймаут (Timeout)

🔴 Пользователь сохраняет большой список
    ↓
⏱️ Запрос превышает 30 секунд
    ↓
🔄 Попытка 1: Ждём 300ms → Retry
    ↓
🔄 Попытка 2: Ждём 600ms → Retry
    ↓
❌ Таймаут снова
    ↓
📢 Показать: "Сервер не отвечает"
    ↓
💡 Рекомендация: "Попробуйте повторить запрос позже"

Сценарий 3: Ошибка валидации (400)

🔴 Пользователь добавляет невалидный домен
    ↓
❌ Backend возвращает 400 Bad Request
    ↓
🚫 Retry не выполняется (ошибка валидации)
    ↓
📢 Показать: "Ошибка валидации данных"
    ↓
💡 Рекомендация: "Проверьте правильность введённых значений"
    ↓
📋 Детали: JSON с полями, которые не прошли валидацию

Сценарий 4: Конфликт ETag (409)

🔴 Пользователь сохраняет изменения
    ↓
⚠️ Другой пользователь изменил данные (ETag не совпадает)
    ↓
❌ Backend возвращает 409 Conflict
    ↓
🚫 Retry не выполняется (конфликт)
    ↓
📢 Показать: "Конфликт данных"
    ↓
💡 Рекомендация: "Обновите страницу и попробуйте снова"

Сценарий 5: Ошибка React компонента

🔴 Ошибка в рендеринге компонента
    ↓
🛡️ ErrorBoundary ловит ошибку
    ↓
📝 Логирование в консоль
    ↓
🎨 Показать fallback UI
    ↓
🔘 Кнопки: "Попробовать снова", "Перезагрузить", "На главную"
    ↓
    ├─ Попробовать снова → Reset state
    ├─ Перезагрузить → window.location.reload()
    └─ На главную → window.location.href = '/'

Сценарий 6: Потеря соединения

🔴 Пользователь работает с приложением
    ↓
📡 navigator.onLine = false (WiFi отключен)
    ↓
🚨 NetworkErrorHandler детектирует событие
    ↓
📢 Показать красный баннер: "Нет соединения с интернетом"
    ↓
⏳ Ожидание восстановления...
    ↓
📡 navigator.onLine = true (WiFi включен)
    ↓
✅ Показать зелёный баннер: "Соединение восстановлено"
    ↓
⏱️ Автоматически скрыть через 3 секунды

Интеграция компонентов

graph LR
    A[Пользовательский<br/>компонент] --> B[useErrorHandler]
    A --> C[RetryButton]
    A --> D[api.js]
    
    B --> E[handleError]
    B --> F[handleSuccess]
    B --> G[withErrorHandler]
    
    D --> H[Request<br/>Interceptor]
    D --> I[Response<br/>Interceptor]
    
    I --> J[Retry Logic]
    I --> K[Error<br/>Formatting]
    
    K --> L[apiErrorHandler]
    L --> M[getErrorType]
    L --> N[formatErrorMessage]
    L --> O[getErrorAction]
    
    J --> P{Success?}
    P -->|Yes| Q[Return Data]
    P -->|No| K
    
    E --> R[Notify System]
    F --> R
    K --> R
    
    R --> S[Toast<br/>Notification]
    
    style A fill:#e3f2fd
    style B fill:#fff3e0
    style D fill:#fce4ec
    style L fill:#f3e5f5
    style R fill:#e8f5e9

Экспорт функциональности

// api.js экспортирует:
export default api;                    // axios instance
export { unwrapStd };                  // utility
export { 
  getErrorType,                        // из errorHandler
  formatErrorMessage,                  // из errorHandler
  getErrorDetails,                     // из errorHandler
  getErrorAction,                      // из errorHandler
  isCriticalError                      // из errorHandler
};

// useErrorHandler.js экспортирует:
export { useErrorHandler };            // основной хук
export { useAsyncError };              // для useEffect

// ErrorBoundary.jsx экспортирует:
export default ErrorBoundary;          // класс компонент

// NetworkErrorHandler.jsx экспортирует:
export default NetworkErrorHandler;    // функциональный компонент

// RetryButton.jsx экспортирует:
export default RetryButton;            // функциональный компонент

Когда использовать что?

Сценарий Решение Пример
Простая загрузка данных withErrorHandler Список доменов
Сложная логика с обработкой handleError вручную Форма с валидацией
Кнопка действия RetryButton Сохранить, Удалить
React Query onError callback useQuery с handleError
Ошибка рендеринга Автоматически ErrorBoundary ловит
Offline состояние Автоматически NetworkErrorHandler

Преимущества архитектуры

Модульность - каждый компонент решает свою задачу
Переиспользуемость - хуки и компоненты можно использовать везде
Тестируемость - изолированная логика легко тестируется
Расширяемость - легко добавить новые типы ошибок
Производительность - минимальный overhead, умный кэш
User Experience - понятные сообщения, автоматический retry
Developer Experience - простой API, хорошая документация


Метрики успеха

  • 📉 Количество необработанных ошибок: 0%
  • 📈 Успешных retry: ~60-70% (зависит от типа ошибки)
  • ⏱️ Среднее время до показа ошибки: 300-1200ms (retry)
  • 🎯 User satisfaction: Значительно выше (понятные сообщения)
  • 🐛 Bugs из-за ошибок: Минимум (ErrorBoundary + логирование)