← Назад к вопросам

Как глобально перехватываешь ошибки от Backend?

2.2 Middle🔥 161 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Глобальный перехват ошибок от 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 пользователя.

Как глобально перехватываешь ошибки от Backend? | PrepBro