Как глобально перехватываешь ошибки от Backend?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Глобальный перехват ошибок от Backend
Глобальный перехват ошибок — это критически важная практика при работе с API. Это позволяет централизованно обрабатывать ошибки, логировать их, показывать пользователю и избежать дублирования кода в каждом запросе.
Проблема: без глобального перехвата
// ❌ Плохо: ошибка обрабатывается везде, где есть запрос
function fetchUsers() {
return fetch('/api/users')
.then(res => res.json())
.catch(err => console.log('Error loading users'));
}
function fetchPosts() {
return fetch('/api/posts')
.then(res => res.json())
.catch(err => console.log('Error loading posts'));
}
function fetchComments() {
return fetch('/api/comments')
.then(res => res.json())
.catch(err => console.log('Error loading comments'));
}
// Много дублирования! И легко забыть обработку в каком-то месте.
Решение 1: API Client с централизованной обработкой
Создаём класс API Client:
// lib/api.ts
class APIClient {
private baseURL = process.env.NEXT_PUBLIC_API_URL;
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
try {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
// Проверяем статус
if (!response.ok) {
await this.handleError(response);
}
return await response.json();
} catch (error) {
this.handleCriticalError(error);
throw error;
}
}
private async handleError(response) {
const status = response.status;
const data = await response.json();
// Обрабатываем разные коды ошибок
switch (status) {
case 400:
console.error('Bad Request:', data.message);
throw new Error(data.message || 'Invalid request');
case 401:
console.error('Unauthorized');
// Перенаправляем на логин
window.location.href = '/login';
break;
case 403:
console.error('Forbidden');
throw new Error('You dont have permission');
case 404:
console.error('Not Found');
throw new Error('Resource not found');
case 500:
console.error('Server Error');
throw new Error('Server error, please try again later');
default:
throw new Error('Unknown error');
}
}
private handleCriticalError(error) {
console.error('Network error:', error);
// Можно отправить в Sentry или другой сервис мониторинга
}
}
export const api = new APIClient();
Используем API Client:
// Просто и лаконично
async function fetchUsers() {
return api.request('/users');
}
async function fetchPosts() {
return api.request('/posts');
}
Решение 2: Middleware/Interceptor (для более сложных случаев)
С Axios (если используешь):
// lib/axiosClient.ts
import axios from 'axios';
const axiosClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
});
// Перехватчик запросов
axiosClient.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Перехватчик ответов
axiosClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Логин истёк
localStorage.removeItem('token');
window.location.href = '/login';
}
if (error.response?.status === 500) {
// Сообщить пользователю
showNotification('Server error', 'error');
}
return Promise.reject(error);
}
);
export default axiosClient;
Используем:
import api from '@/lib/axiosClient';
const { data } = await api.get('/users');
Решение 3: Error Boundary (для React)
Error Boundary ловит ошибки в компонентах:
// components/ErrorBoundary.tsx
import React from 'react';
interface Props {
children: React.ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
export class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// Логируем ошибку
console.error('Error caught by boundary:', error, errorInfo);
// Отправляем в сервис мониторинга (Sentry, LogRocket)
}
render() {
if (this.state.hasError) {
return (
<div className="error-container">
<h1>Something went wrong</h1>
<p>{this.state.error?.message}</p>
<button onClick={() => window.location.reload()}>Reload</button>
</div>
);
}
return this.props.children;
}
}
Используем:
// app.tsx
<ErrorBoundary>
<App />
</ErrorBoundary>
Решение 4: Context API + Hook для глобальной обработки
Создаём Context для ошибок:
// contexts/ErrorContext.tsx
import React, { createContext, useState } from 'react';
interface Error {
message: string;
code?: number;
timestamp: number;
}
interface ErrorContextType {
errors: Error[];
addError: (message: string, code?: number) => void;
clearErrors: () => void;
removeError: (index: number) => void;
}
export const ErrorContext = createContext<ErrorContextType | null>(null);
export function ErrorProvider({ children }) {
const [errors, setErrors] = useState<Error[]>([]);
const addError = (message: string, code?: number) => {
const error: Error = {
message,
code,
timestamp: Date.now(),
};
setErrors(prev => [...prev, error]);
// Автоматически удаляем ошибку через 5 секунд
setTimeout(() => {
setErrors(prev => prev.filter(e => e.timestamp !== error.timestamp));
}, 5000);
};
const clearErrors = () => setErrors([]);
const removeError = (index: number) => {
setErrors(prev => prev.filter((_, i) => i !== index));
};
return (
<ErrorContext.Provider value={{ errors, addError, clearErrors, removeError }}>
{children}
</ErrorContext.Provider>
);
}
Hook для использования:
// hooks/useError.ts
import { useContext } from 'react';
import { ErrorContext } from '@/contexts/ErrorContext';
export function useError() {
const context = useContext(ErrorContext);
if (!context) {
throw new Error('useError must be used within ErrorProvider');
}
return context;
}
Используем в компонентах:
function LoginForm() {
const { addError } = useError();
const handleLogin = async (email, password) => {
try {
const response = await api.post('/auth/login', { email, password });
// успешный логин
} catch (error) {
addError(error.message, error.code);
}
};
return <form onSubmit={handleLogin}>{...}</form>;
}
Компонент для отображения ошибок:
// components/ErrorNotifications.tsx
import { useError } from '@/hooks/useError';
export function ErrorNotifications() {
const { errors, removeError } = useError();
return (
<div className="error-notifications">
{errors.map((error, index) => (
<div key={index} className="error-toast">
<span>{error.message}</span>
<button onClick={() => removeError(index)}>×</button>
</div>
))}
</div>
);
}
Обработка разных типов ошибок
// lib/api.ts
class APIError extends Error {
constructor(public status: number, public data: any) {
super(data.message || 'Unknown error');
}
}
class APIClient {
async request(endpoint: string, options = {}) {
const response = await fetch(endpoint, options);
if (!response.ok) {
const data = await response.json();
if (response.status === 401) {
// Перенаправить на логин
window.location.href = '/login';
}
if (response.status >= 500) {
// Отправить в Sentry
Sentry.captureException(new APIError(response.status, data));
}
throw new APIError(response.status, data);
}
return response.json();
}
}
На собеседовании
Краткий ответ: Создаю API Client (класс или функция), который оборачивает все запросы. В нём централизованно обрабатываю ошибки: логирую, показываю пользователю, обрабатываю специфические коды (401, 500 и т.д.). Также использую Error Boundary в React для ловли ошибок в компонентах.
Развёрнутый ответ: Я использую несколько слоёв перехвата: 1) API Client для обработки HTTP ошибок — там проверяю статус, обрабатываю 401 (перенаправляю на логин), 500 (показываю сообщение пользователю); 2) Error Boundary в React для ловли ошибок в компонентах; 3) Context API с custom hook для доступа к ошибкам из любого компонента; 4) Отправка критических ошибок в сервис мониторинга (Sentry). Это позволяет избежать дублирования кода и обеспечивает хорошую experience пользователя.