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

SkateJS — это современная библиотека для создания веб-компонентов на базе Web Components и реактивных паттернов. Одной из критически важных задач при работе с компонентами, взаимодействующими с сетью, является обработка ошибок сетевых запросов. Неправильная обработка может привести к неконсистентному состоянию компонента, зависаниям интерфейса или некорректному отображению данных.


Асинхронные операции и реактивные свойства

В SkateJS реактивные свойства объявляются через props или хук useState. При работе с сетевыми запросами важно разделять состояние запроса и состояние ошибки:

import { Component, h, prop } from 'skatejs';

class UserProfile extends Component {
  static props = {
    userId: prop.number,
    userData: prop.object,
    error: prop.string
  };

  connectedCallback() {
    super.connectedCallback();
    this.fetchUserData();
  }

  async fetchUserData() {
    this.error = null; // Сброс ошибки перед запросом
    try {
      const response = await fetch(`https://api.example.com/users/${this.userId}`);
      if (!response.ok) {
        throw new Error(`Ошибка сети: ${response.status}`);
      }
      this.userData = await response.json();
    } catch (err) {
      this.error = err.message;
    }
  }

  render() {
    if (this.error) {
      return <div class="error">{this.error}</div>;
    }
    if (!this.userData) {
      return <div>Загрузка...</div>;
    }
    return <div>{this.userData.name}</div>;
  }
}

customElements.define('user-profile', UserProfile);

Ключевые моменты:

  • Свойство error используется для хранения информации о сетевой ошибке.
  • Использование try...catch гарантирует перехват как сетевых ошибок (fetch), так и ошибок обработки ответа (response.json()).
  • Сброс error перед новым запросом предотвращает отображение устаревших сообщений.

Повторные попытки при сетевых сбоях

Для повышения устойчивости приложения полезно реализовать механизм повторных попыток (retry):

async fetchWithRetry(url, retries = 3, delay = 1000) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`Ошибка: ${response.status}`);
      return await response.json();
    } catch (err) {
      if (i === retries - 1) throw err;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}
  • retries определяет количество повторных попыток.
  • delay задает паузу между попытками.
  • Ошибки перехватываются и только после последней попытки выбрасываются наружу.

SkateJS-компонент может использовать этот метод так же, как обычный fetch, сохраняя реактивное обновление состояния:

this.userData = await this.fetchWithRetry(`https://api.example.com/users/${this.userId}`);

Визуальное отображение ошибок

Эффективная обработка ошибок сети включает наглядное отображение состояния ошибки пользователю:

  • Простое сообщение об ошибке — используется для уведомления о неудачном запросе.
  • Интерактивные элементы для повторного запроса — кнопка «Попробовать снова» позволяет инициировать повторный fetch.
render() {
  if (this.error) {
    return (
      <div class="error">
        {this.error}
        <button onCl ick={() => this.fetchUserData()}>Попробовать снова</button>
      </div>
    );
  }
  if (!this.userData) {
    return <div>Загрузка...</div>;
  }
  return <div>{this.userData.name}</div>;
}

Обработка отмены запросов

При изменении свойств компонента или быстром размонтировании важно отменять устаревшие сетевые запросы, чтобы избежать состояния гонки:

connectedCallback() {
  super.connectedCallback();
  this.controller = new AbortController();
  this.fetchUserData();
}

disconnectedCallback() {
  super.disconnectedCallback();
  if (this.controller) {
    this.controller.abort();
  }
}

async fetchUserData() {
  this.error = null;
  try {
    const response = await fetch(
      `https://api.example.com/users/${this.userId}`,
      { signal: this.controller.signal }
    );
    if (!response.ok) throw new Error(`Ошибка сети: ${response.status}`);
    this.userData = await response.json();
  } catch (err) {
    if (err.name !== 'AbortError') {
      this.error = err.message;
    }
  }
}
  • Используется AbortController для контроля жизненного цикла запроса.
  • Ошибки, вызванные отменой (AbortError), игнорируются.
  • Предотвращается обновление состояния после удаления компонента из DOM.

Стратегии обработки ошибок по типам

Ошибки сети могут быть разного типа:

  1. HTTP ошибки (4xx, 5xx)

    • Важно различать клиентские ошибки (например, 404) и серверные (500+).
    • Можно сохранять код ошибки и отображать кастомные сообщения.
  2. Сетевые сбои (timeout, offline)

    • Использовать события navigator.onLine и обработку TypeError при fetch.
    • Возможна локальная буферизация запросов для повторной отправки при восстановлении соединения.
  3. Ошибки данных (невалидный JSON)

    • Использовать отдельный try...catch для response.json()
    • Позволяет отличать сетевую ошибку от ошибки формата данных.

Рекомендации по архитектуре компонентов

  • Разделение данных и визуализации: хранить loading, error и data отдельно для чистой логики рендера.
  • Использование реактивных свойств SkateJS: любые изменения prop автоматически перерисовывают компонент, что исключает ручное управление DOM.
  • Повторное использование методов fetch: вынос сетевых вызовов в отдельные функции позволяет централизованно управлять retry, timeout и логированием ошибок.

Обработка ошибок сети в SkateJS требует комбинации реактивного управления состоянием, асинхронных операций и контроля жизненного цикла компонентов. Следуя описанным подходам, компоненты становятся устойчивыми, предсказуемыми и безопасными в работе с нестабильными сетевыми условиями.