Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m43s
423 lines
14 KiB
Markdown
423 lines
14 KiB
Markdown
# 🔄 Схема работы системы обработки ошибок
|
||
|
||
## Поток обработки ошибок
|
||
|
||
```mermaid
|
||
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
|
||
```
|
||
|
||
---
|
||
|
||
## Структура компонентов
|
||
|
||
```mermaid
|
||
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
|
||
```
|
||
|
||
---
|
||
|
||
## Жизненный цикл запроса с ошибкой
|
||
|
||
```mermaid
|
||
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
|
||
|
||
```mermaid
|
||
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 секунды
|
||
```
|
||
|
||
---
|
||
|
||
## Интеграция компонентов
|
||
|
||
```mermaid
|
||
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
|
||
```
|
||
|
||
---
|
||
|
||
## Экспорт функциональности
|
||
|
||
```javascript
|
||
// 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 + логирование)
|
||
|