Обработка ошибок сети

Работа с сетевыми запросами является критически важной частью современных веб-приложений. Preact, как легковесный фреймворк для построения интерфейсов, не накладывает ограничений на способы обработки сетевых ошибок, но предоставляет удобные возможности интеграции с асинхронными операциями и управлением состоянием.

Асинхронные операции и fetch

Основной инструмент для выполнения сетевых запросов в Preact — это стандартный API fetch. Взаимодействие с ним требует внимательного подхода к обработке ошибок, так как ошибки могут возникнуть на разных уровнях:

  1. Сетевые ошибки — отказ соединения, таймаут, DNS-сбой.
  2. Ошибки протокола HTTP — получение статусов 4xx и 5xx.
  3. Ошибки обработки данных — неправильный формат ответа (например, некорректный JSON).

Пример безопасного запроса с обработкой ошибок:

import { useState, useEffect } from 'preact/hooks';

function DataFetcher({ url }) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchData() {
      setLoading(true);
      setError(null);
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`Ошибка HTTP: ${response.status}`);
        }
        const json = await response.json();
        setData(json);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }
    fetchData();
  }, [url]);

  if (loading) return <p>Загрузка...</p>;
  if (error) return <p>Произошла ошибка: {error}</p>;
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

В этом примере ключевым моментом является разделение состояния загрузки, данных и ошибки, что обеспечивает предсказуемое поведение компонента.

Использование try/catch внутри эффектов

Ошибки асинхронных операций необходимо оборачивать в блоки try/catch, чтобы предотвратить падение всего приложения. В Preact это особенно важно при работе с хуками useEffect, так как необработанные исключения могут привести к некорректному обновлению DOM.

useEffect(() => {
  const controller = new AbortController();

  async function load() {
    try {
      const res = await fetch('/api/data', { signal: controller.signal });
      if (!res.ok) throw new Error(`HTTP error ${res.status}`);
      const result = await res.json();
      setData(result);
    } catch (err) {
      if (err.name === 'AbortError') return;
      setError(err.message);
    }
  }

  load();
  return () => controller.abort();
}, []);

Использование AbortController позволяет безопасно прерывать запросы при размонтировании компонента и предотвращает ошибки при попытке обновления состояния уже после удаления компонента из DOM.

Составное управление состоянием ошибок

Для крупных приложений удобным подходом является выделение отдельного состояния для ошибок и их типов:

const [state, setState] = useState({
  data: null,
  loading: false,
  networkError: null,
  validationError: null
});

async function fetchData() {
  setState(prev => ({ ...prev, loading: true, networkError: null }));
  try {
    const response = await fetch('/api/items');
    if (!response.ok) throw new Error('Network response was not ok');
    const data = await response.json();
    setState(prev => ({ ...prev, data }));
  } catch (err) {
    setState(prev => ({ ...prev, networkError: err.message }));
  } finally {
    setState(prev => ({ ...prev, loading: false }));
  }
}

Разделение ошибок по категориям позволяет гибко реагировать на разные сценарии: повторный запрос при сетевых сбоях или отображение сообщений валидации.

Глобальная обработка ошибок

Для обработки ошибок на уровне всего приложения применяются методы типа Error Boundaries, доступные через компонент ErrorBoundary. Хотя Preact не поддерживает их в точности как React, существуют легковесные реализации:

import { Component } from 'preact';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  componentDidCatch(error) {
    this.setState({ hasError: true, error });
    console.error(error);
  }

  render() {
    if (this.state.hasError) {
      return <p>Произошла критическая ошибка: {this.state.error.message}</p>;
    }
    return this.props.children;
  }
}

Использование такого компонента позволяет перехватывать непредвиденные ошибки во всех дочерних компонентах, в том числе возникающие при сетевых запросах, и предотвращать падение приложения.

Ретрай и таймауты

Для повышения устойчивости к нестабильным сетям применяются повторные попытки запросов и таймауты:

async function fetchWithRetry(url, retries = 3, delay = 1000) {
  for (let i = 0; i < retries; i++) {
    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 5000);
      const response = await fetch(url, { signal: controller.signal });
      clearTimeout(timeoutId);
      if (!response.ok) throw new Error(`HTTP error ${response.status}`);
      return await response.json();
    } catch (err) {
      if (i === retries - 1) throw err;
    }
    await new Promise(resolve => setTimeout(resolve, delay));
  }
}

Этот подход позволяет автоматически повторять запросы при временных сбоях, избегая необходимости ручного вмешательства.

Интеграция с состоянием приложения

Обработка сетевых ошибок в Preact часто сочетается с глобальным состоянием через useContext или сторонние менеджеры состояния. Это позволяет централизованно хранить состояние ошибок и управлять уведомлениями пользователя. Пример:

import { createContext } from 'preact';
import { useContext } from 'preact/hooks';

const ErrorContext = createContext();

function ErrorProvider({ children }) {
  const [error, setError] = useState(null);
  return (
    <ErrorContext.Provider value={{ error, setError }}>
      {children}
    </ErrorContext.Provider>
  );
}

function useError() {
  return useContext(ErrorContext);
}

Таким образом, компоненты могут обновлять и получать состояние ошибок без дублирования кода.


Обработка сетевых ошибок в Preact строится на четком разделении состояний загрузки, данных и ошибок, использовании асинхронных блоков try/catch, применении таймаутов и повторных попыток, а также на возможности централизованного управления ошибками через контекст или ErrorBoundary. Такой подход обеспечивает стабильность интерфейса и предсказуемость поведения приложения в условиях нестабильного соединения.