# π‘οΈ Π ΡΠΊΠΎΠ²ΠΎΠ΄ΡΡΠ²ΠΎ ΠΏΠΎ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ΅ ΠΎΡΠΈΠ±ΠΎΠΊ
ΠΠΎΠΌΠΏΠ»Π΅ΠΊΡΠ½Π°Ρ ΡΠΈΡΡΠ΅ΠΌΠ° ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΎΡΠΈΠ±ΠΎΠΊ Π΄Π»Ρ Router Lists UI, Π²ΠΊΠ»ΡΡΠ°ΡΡΠ°Ρ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈΠΉ retry, user-friendly ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ ΠΈ ΠΌΠΎΠ½ΠΈΡΠΎΡΠΈΠ½Π³ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ ΡΠ΅ΡΠΈ.
## π ΠΠ³Π»Π°Π²Π»Π΅Π½ΠΈΠ΅
- [ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ ΡΠΈΡΡΠ΅ΠΌΡ](#ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ-ΡΠΈΡΡΠ΅ΠΌΡ)
- [Π’ΠΈΠΏΡ ΠΎΡΠΈΠ±ΠΎΠΊ](#ΡΠΈΠΏΡ-ΠΎΡΠΈΠ±ΠΎΠΊ)
- [ΠΠ²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈΠΉ Retry](#Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈΠΉ-retry)
- [ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°Ρ
](#ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅-Π²-ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°Ρ
)
- [API Reference](#api-reference)
- [ΠΡΠΈΠΌΠ΅ΡΡ](#ΠΏΡΠΈΠΌΠ΅ΡΡ)
- [ΠΡΡΡΠΈΠ΅ ΠΏΡΠ°ΠΊΡΠΈΠΊΠΈ](#Π»ΡΡΡΠΈΠ΅-ΠΏΡΠ°ΠΊΡΠΈΠΊΠΈ)
---
## π§© ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ ΡΠΈΡΡΠ΅ΠΌΡ
### 1. **ErrorBoundary**
ΠΠ»ΠΎΠ±Π°Π»ΡΠ½ΡΠΉ ΠΎΠ±ΡΠ°Π±ΠΎΡΡΠΈΠΊ ΠΎΡΠΈΠ±ΠΎΠΊ React Π΄Π»Ρ Π»ΠΎΠ²Π»ΠΈ ΠΎΡΠΈΠ±ΠΎΠΊ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π°.
**ΠΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ:**
- β
ΠΠΎΠ²ΠΈΡ ΠΎΡΠΈΠ±ΠΊΠΈ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π° ΠΈ ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°Π΅Ρ fallback UI
- β
ΠΠΎΠ³ΠΈΡΡΠ΅Ρ ΠΎΡΠΈΠ±ΠΊΠΈ Π² ΠΊΠΎΠ½ΡΠΎΠ»Ρ ΠΈ ΡΠΈΡΡΠ΅ΠΌΡ ΠΌΠΎΠ½ΠΈΡΠΎΡΠΈΠ½Π³Π°
- β
ΠΠΎΠΊΠ°Π·ΡΠ²Π°Π΅Ρ Π΄Π΅ΡΠ°Π»ΠΈ ΠΎΡΠΈΠ±ΠΊΠΈ Π² dev ΡΠ΅ΠΆΠΈΠΌΠ΅
- β
ΠΠ²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ ΠΎΡΠΈΡΠ°Π΅Ρ ΠΊΡΡ ΠΏΡΠΈ ΠΏΠΎΠ²ΡΠΎΡΡΡΡΠΈΡ
ΡΡ ΠΎΡΠΈΠ±ΠΊΠ°Ρ
- β
ΠΠ½ΠΎΠΏΠΊΠΈ Π²ΠΎΡΡΡΠ°Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ: "ΠΠΎΠΏΡΠΎΠ±ΠΎΠ²Π°ΡΡ ΡΠ½ΠΎΠ²Π°", "ΠΠ΅ΡΠ΅Π·Π°Π³ΡΡΠ·ΠΈΡΡ", "ΠΠ° Π³Π»Π°Π²Π½ΡΡ"
**ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅:**
```jsx
// Π£ΠΆΠ΅ Π²ΡΡΡΠΎΠ΅Π½ Π² App.jsx, ΠΎΠ±ΠΎΡΠ°ΡΠΈΠ²Π°Π΅Ρ Π²ΡΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅
```
### 2. **NetworkErrorHandler**
ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Π΄Π»Ρ ΠΌΠΎΠ½ΠΈΡΠΎΡΠΈΠ½Π³Π° ΡΠΎΡΡΠΎΡΠ½ΠΈΡ ΡΠ΅ΡΠΈ.
**ΠΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ:**
- β
ΠΠ΅ΡΠ΅ΠΊΡΠΈΡΡΠ΅Ρ offline/online ΡΠΎΠ±ΡΡΠΈΡ
- β
ΠΠΎΠΊΠ°Π·ΡΠ²Π°Π΅Ρ Π±Π°Π½Π½Π΅Ρ ΠΏΡΠΈ ΠΏΠΎΡΠ΅ΡΠ΅ ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΡ
- β
Π£Π²Π΅Π΄ΠΎΠΌΠ»ΡΠ΅Ρ ΠΎ Π²ΠΎΡΡΡΠ°Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΈ ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΡ
- β
ΠΠ½ΡΠ΅Π³ΡΠΈΡΡΠ΅ΡΡΡ Ρ Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΠΎΠΉ ΡΠΈΡΡΠ΅ΠΌΠΎΠΉ ΡΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠΉ
**ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅:**
```jsx
// Π£ΠΆΠ΅ Π²ΡΡΡΠΎΠ΅Π½ Π² App.jsx
```
### 3. **apiErrorHandler.js**
Π£ΡΠΈΠ»ΠΈΡΡ Π΄Π»Ρ ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ ΠΈ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΎΡΠΈΠ±ΠΎΠΊ API.
**Π€ΡΠ½ΠΊΡΠΈΠΈ:**
- `getErrorType(error)` - ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ΅Ρ ΡΠΈΠΏ ΠΎΡΠΈΠ±ΠΊΠΈ
- `isRetriableError(error, method)` - ΠΏΡΠΎΠ²Π΅ΡΡΠ΅Ρ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡ retry
- `getRetryDelay(attemptNumber)` - Π²ΡΡΠΈΡΠ»ΡΠ΅Ρ Π·Π°Π΄Π΅ΡΠΆΠΊΡ Π΄Π»Ρ retry
- `getMaxRetries(errorType, method)` - Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΠΌΠ°ΠΊΡΠΈΠΌΡΠΌ ΠΏΠΎΠΏΡΡΠΎΠΊ
- `formatErrorMessage(error)` - ΡΠΎΡΠΌΠ°ΡΠΈΡΡΠ΅Ρ user-friendly ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅
- `getErrorDetails(error)` - ΠΈΠ·Π²Π»Π΅ΠΊΠ°Π΅Ρ Π΄Π΅ΡΠ°Π»ΠΈ ΠΎΡΠΈΠ±ΠΊΠΈ
- `getErrorAction(error)` - Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΡΠ΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°ΡΠΈΠΈ ΠΏΠΎ ΡΡΡΡΠ°Π½Π΅Π½ΠΈΡ
- `isCriticalError(error)` - ΠΏΡΠΎΠ²Π΅ΡΡΠ΅Ρ ΠΊΡΠΈΡΠΈΡΠ½ΠΎΡΡΡ ΠΎΡΠΈΠ±ΠΊΠΈ
- `logError(error, context)` - Π»ΠΎΠ³ΠΈΡΡΠ΅Ρ ΠΎΡΠΈΠ±ΠΊΡ
### 4. **useErrorHandler**
React Ρ
ΡΠΊ Π΄Π»Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΎΡΠΈΠ±ΠΎΠΊ Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°Ρ
.
**API:**
```javascript
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: Π ΡΡΠ½Π°Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ°
```jsx
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 (
);
}
```
### ΠΠ°ΡΠΈΠ°Π½Ρ 2: ΠΠ²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠ°Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ°
```jsx
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 (
);
}
```
### ΠΠ°ΡΠΈΠ°Π½Ρ 3: Π‘ RetryButton
```jsx
import RetryButton from '../components/RetryButton';
import api from '../lib/api';
function MyComponent() {
const saveData = async () => {
await api.post('/domains-new', { domains: [...] });
};
return (
Π‘ΠΎΡ
ΡΠ°Π½ΠΈΡΡ
);
}
```
### ΠΠ°ΡΠΈΠ°Π½Ρ 4: React Query ΠΈΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΡ
```jsx
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
{/* ... */}
;
}
```
---
## π API Reference
### useErrorHandler()
```typescript
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()
```javascript
import { getErrorType, ErrorType } from '../lib/api';
const error = new Error('Network error');
const type = getErrorType(error);
if (type === ErrorType.NETWORK) {
console.log('ΠΡΠΎΠ±Π»Π΅ΠΌΡ Ρ ΡΠ΅ΡΡΡ');
}
```
### formatErrorMessage()
```javascript
import { formatErrorMessage } from '../lib/api';
try {
await api.get('/endpoint');
} catch (error) {
const message = formatErrorMessage(error);
// "ΠΠ΅ ΡΠ΄Π°Π»ΠΎΡΡ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡΡΡ ΠΊ ΡΠ΅ΡΠ²Π΅ΡΡ. ΠΡΠΎΠ²Π΅ΡΡΡΠ΅ ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΠ΅."
alert(message);
}
```
### getErrorAction()
```javascript
import { getErrorAction } from '../lib/api';
try {
await api.post('/endpoint', data);
} catch (error) {
const action = getErrorAction(error);
// "ΠΡΠΎΠ²Π΅ΡΡΡΠ΅ ΠΏΡΠ°Π²ΠΈΠ»ΡΠ½ΠΎΡΡΡ Π²Π²Π΅Π΄ΡΠ½Π½ΡΡ
Π΄Π°Π½Π½ΡΡ
."
console.log('Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°ΡΠΈΡ:', action);
}
```
---
## π― ΠΡΠΈΠΌΠ΅ΡΡ
### ΠΡΠΈΠΌΠ΅Ρ 1: ΠΠ°Π³ΡΡΠ·ΠΊΠ° Π΄Π°Π½Π½ΡΡ
Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΎΠΉ ΠΎΡΠΈΠ±ΠΎΠΊ
```jsx
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 (
{!data ? (
ΠΠ°Π³ΡΡΠ·ΠΈΡΡ Π΄Π°Π½Π½ΡΠ΅
) : (
ΠΠ°Π³ΡΡΠΆΠ΅Π½ΠΎ: {data.items.length} Π·Π°ΠΏΠΈΡΠ΅ΠΉ
)}
);
}
```
### ΠΡΠΈΠΌΠ΅Ρ 2: Π€ΠΎΡΠΌΠ° Ρ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΠ΅ΠΉ
```jsx
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 (
);
}
```
### ΠΡΠΈΠΌΠ΅Ρ 3: ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΊΡΠΈΡΠΈΡΠ½ΡΡ
ΠΎΡΠΈΠ±ΠΎΠΊ
```jsx
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 ...
;
}
```
---
## β
ΠΡΡΡΠΈΠ΅ ΠΏΡΠ°ΠΊΡΠΈΠΊΠΈ
### 1. ΠΡΠ΅Π³Π΄Π° ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ useErrorHandler
```jsx
// β
Π₯ΠΎΡΠΎΡΠΎ
import { useErrorHandler } from '../hooks/useErrorHandler';
function MyComponent() {
const { handleError } = useErrorHandler();
// ...
}
// β ΠΠ»ΠΎΡ
ΠΎ
function MyComponent() {
const handleError = (error) => {
alert(error.message); // ΠΠ΅ user-friendly
};
}
```
### 2. ΠΠ΅ΡΠ΅Π΄Π°Π²Π°ΠΉΡΠ΅ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ Π² handleError
```jsx
// β
Π₯ΠΎΡΠΎΡΠΎ
handleError(error, {
component: 'DomainsManager',
action: 'delete',
itemId: domain.id
});
// β ΠΠ»ΠΎΡ
ΠΎ
handleError(error);
```
### 3. ΠΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ withErrorHandler Π΄Π»Ρ ΠΏΡΠΎΡΡΡΡ
ΡΠ»ΡΡΠ°Π΅Π²
```jsx
// β
Π₯ΠΎΡΠΎΡΠΎ - Π»Π°ΠΊΠΎΠ½ΠΈΡΠ½ΠΎ
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. ΠΠ΅ Π΄ΡΠ±Π»ΠΈΡΡΠΉΡΠ΅ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΡ ΠΎΡΠΈΠ±ΠΎΠΊ
```jsx
// β
Π₯ΠΎΡΠΎΡΠΎ - API Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°Π΅Ρ ΡΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠ΅
try {
await api.post('/data', payload);
} catch (error) {
// ΠΡΠΈΠ±ΠΊΠ° ΡΠΆΠ΅ ΠΏΠΎΠΊΠ°Π·Π°Π½Π° ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ
// ΠΠ΄Π΅ΡΡ ΡΠΎΠ»ΡΠΊΠΎ Π»ΠΎΠΊΠ°Π»ΡΠ½Π°Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ°
setLoading(false);
}
// β ΠΠ»ΠΎΡ
ΠΎ - Π΄Π²ΠΎΠΉΠ½ΠΎΠ΅ ΡΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠ΅
try {
await api.post('/data', payload);
} catch (error) {
handleError(error); // Π£Π²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠ΅ ΠΏΠΎΠΊΠ°Π·Π°Π½ΠΎ Π΄Π²Π°ΠΆΠ΄Ρ!
}
```
### 5. ΠΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ RetryButton Π΄Π»Ρ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΉ
```jsx
// β
Π₯ΠΎΡΠΎΡΠΎ
Π‘ΠΎΡ
ΡΠ°Π½ΠΈΡΡ
// β ΠΠ»ΠΎΡ
ΠΎ - ΡΡΡΠ½Π°Ρ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ retry
```
### 6. ΠΠΎΠ³ΠΈΡΡΠΉΡΠ΅ ΠΊΡΠΈΡΠΈΡΠ½ΡΠ΅ ΠΎΡΠΈΠ±ΠΊΠΈ
```jsx
// β
Π₯ΠΎΡΠΎΡΠΎ
import { isCriticalError, logError } from '../lib/api';
catch (error) {
if (isCriticalError(error)) {
logError(error, { critical: true, user: currentUser });
}
handleError(error);
}
```
---
## π§ ΠΠ°ΡΡΡΠΎΠΉΠΊΠ°
### ΠΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ ΡΠ°ΠΉΠΌΠ°ΡΡΠ°
```javascript
// frontend/src/lib/api.js
const api = axios.create({
timeout: 30000, // 30 ΡΠ΅ΠΊΡΠ½Π΄ (ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ)
});
```
### ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° retry ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΎΠ²
```javascript
// 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
```javascript
// 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'
});
}
}
```
---
## π ΠΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΡΠ΅ ΡΠ΅ΡΡΡΡΡ
- [ΠΡΠΈΠΌΠ΅ΡΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ](./frontend/src/examples/ErrorHandlingExample.jsx)
- [Axios Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ](https://axios-http.com/docs/handling_errors)
- [React Error Boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)
- [Exponential Backoff](https://en.wikipedia.org/wiki/Exponential_backoff)
---
## π Changelog
### v1.0.0 (2025-10-03)
- β
ΠΠΎΠ±Π°Π²Π»Π΅Π½ ErrorBoundary Π΄Π»Ρ React ΠΎΡΠΈΠ±ΠΎΠΊ
- β
ΠΠΎΠ±Π°Π²Π»Π΅Π½ NetworkErrorHandler Π΄Π»Ρ offline/online
- β
Π£Π»ΡΡΡΠ΅Π½ retry ΠΌΠ΅Ρ
Π°Π½ΠΈΠ·ΠΌ Ρ exponential backoff
- β
ΠΠΎΠ±Π°Π²Π»Π΅Π½Π° ΡΠΈΠΏΠΈΠ·Π°ΡΠΈΡ ΠΎΡΠΈΠ±ΠΎΠΊ (11 ΡΠΈΠΏΠΎΠ²)
- β
User-friendly ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ Π²ΠΌΠ΅ΡΡΠΎ ΡΠ΅Ρ
Π½ΠΈΡΠ΅ΡΠΊΠΈΡ
- β
Π₯ΡΠΊ useErrorHandler Π΄Π»Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ²
- β
ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ RetryButton
- β
ΠΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΡ Ρ ΡΠΈΡΡΠ΅ΠΌΠΎΠΉ ΡΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠΉ
- β
ΠΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΊΡΠΈΡΠΈΡΠ½ΡΡ
ΠΎΡΠΈΠ±ΠΎΠΊ
- β
Π£Π²Π΅Π»ΠΈΡΠ΅Π½ ΡΠ°ΠΉΠΌΠ°ΡΡ Π΄ΠΎ 30 ΡΠ΅ΠΊΡΠ½Π΄
---
## π€ ΠΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠ°
ΠΡΠ»ΠΈ Ρ Π²Π°Ρ Π²ΠΎΠ·Π½ΠΈΠΊΠ»ΠΈ Π²ΠΎΠΏΡΠΎΡΡ ΠΈΠ»ΠΈ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ Ρ ΡΠΈΡΡΠ΅ΠΌΠΎΠΉ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΎΡΠΈΠ±ΠΎΠΊ:
1. ΠΡΠΎΠ²Π΅ΡΡΡΠ΅ ΠΊΠΎΠ½ΡΠΎΠ»Ρ Π±ΡΠ°ΡΠ·Π΅ΡΠ° Π½Π° Π½Π°Π»ΠΈΡΠΈΠ΅ ΠΎΡΠΈΠ±ΠΎΠΊ
2. ΠΡΠΎΠ²Π΅ΡΡΡΠ΅ Π²ΠΊΠ»Π°Π΄ΠΊΡ Network Π² DevTools
3. Π£Π±Π΅Π΄ΠΈΡΠ΅ΡΡ ΡΡΠΎ backend ΡΠ΅ΡΠ²Π΅Ρ Π·Π°ΠΏΡΡΠ΅Π½
4. ΠΡΠΎΠ²Π΅ΡΡΡΠ΅ Π½Π°ΡΡΡΠΎΠΉΠΊΠΈ CORS
**ΠΠΎΠ»Π΅Π·Π½ΡΠ΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ Π΄Π»Ρ ΠΎΡΠ»Π°Π΄ΠΊΠΈ:**
```javascript
// Π ΠΊΠΎΠ½ΡΠΎΠ»ΠΈ Π±ΡΠ°ΡΠ·Π΅ΡΠ°
localStorage.clear(); // ΠΡΠΈΡΡΠΈΡΡ ΠΊΡΡ
window.notify.clear(); // ΠΡΠΈΡΡΠΈΡΡ ΡΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΡ
console.log(window.notify); // ΠΡΠΎΠ²Π΅ΡΠΈΡΡ ΡΠΈΡΡΠ΅ΠΌΡ ΡΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠΉ
```