Обработка ошибок HTTP

Stimulus изначально ориентирован на минимализм и тесную интеграцию с серверным рендерингом. В отличие от крупных SPA-фреймворков, он не навязывает собственный слой работы с сетью. HTTP-запросы чаще всего выполняются через fetch, axios или вспомогательные библиотеки, а Stimulus-контроллеры отвечают за реакцию интерфейса на результат этих запросов. Именно поэтому корректная обработка HTTP-ошибок становится зоной ответственности разработчика и важной частью архитектуры контроллеров.

HTTP-ошибка — это не только код ответа 4xx или 5xx, но и целый спектр ситуаций: сетевые сбои, таймауты, некорректный JSON, неожиданная структура данных. Stimulus предоставляет инструменты для аккуратного управления состоянием интерфейса в таких сценариях.


Базовый подход: fetch и проверка статуса ответа

Классический сценарий — отправка запроса внутри метода контроллера:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  async submit() {
    const response = await fetch("/api/items", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name: "Item" })
    })

    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`)
    }

    const data = await response.json()
    this.handleSuccess(data)
  }

  handleSuccess(data) {
    // обработка успешного ответа
  }
}

Ключевой момент — проверка response.ok. Fetch не выбрасывает исключение при кодах 4xx и 5xx, поэтому отсутствие явной проверки приводит к ложному ощущению успешного запроса.


Перехват ошибок через try/catch

Для контроллеров Stimulus предпочтителен централизованный перехват ошибок внутри асинхронного метода:

async submit() {
  try {
    const response = await fetch(this.urlValue)

    if (!response.ok) {
      await this.handleHttpError(response)
      return
    }

    const data = await response.json()
    this.renderData(data)
  } catch (error) {
    this.handleNetworkError(error)
  }
}

Такой подход позволяет разделить:

  • HTTP-ошибки — сервер ответил, но с кодом ошибки
  • Сетевые ошибки — запрос не был выполнен физически

Это разделение принципиально важно для корректного UX.


Обработка ошибок по классам HTTP-кодов

Часто требуется различное поведение для разных диапазонов кодов:

async handleHttpError(response) {
  if (response.status === 401) {
    this.showAuthError()
  } else if (response.status === 403) {
    this.showForbiddenError()
  } else if (response.status === 422) {
    const errors = await response.json()
    this.showValidationErrors(errors)
  } else if (response.status >= 500) {
    this.showServerError()
  }
}

Практика:

  • 401 — проблемы аутентификации
  • 403 — отсутствие прав
  • 422 — ошибки валидации формы
  • 5xx — серверные сбои

Stimulus-контроллер в таком случае выступает как слой интерпретации ответа, а не просто обработчик кликов.


Использование targets для отображения ошибок

Stimulus targets позволяют привязать обработку ошибок напрямую к DOM-элементам:

<div data-controller="form">
  <div data-form-target="error"></div>
  <form data-action="submit->form#submit">
    <!-- поля формы -->
  </form>
</div>
export default class extends Controller {
  static targets = ["error"]

  showValidationErrors(errors) {
    this.errorTarget.innerHTML = errors.join("<br>")
    this.errorTarget.classList.remove("hidden")
  }
}

Преимущество подхода — отсутствие глобального состояния и минимальная связность кода.


Состояние загрузки и ошибки

Корректная обработка ошибок невозможна без управления состояниями:

  • загрузка
  • успех
  • ошибка

В Stimulus это обычно реализуется через CSS-классы:

async submit() {
  this.element.classList.add("loading")

  try {
    const response = await fetch(this.urlValue)

    if (!response.ok) {
      throw response
    }

    this.element.classList.remove("loading")
    this.element.classList.add("success")
  } catch (error) {
    this.element.classList.remove("loading")
    this.element.classList.add("error")
  }
}

Такой шаблон упрощает визуальное сопровождение ошибок без усложнения логики.


Повторное использование логики обработки ошибок

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

export default class BaseController extends Controller {
  async handleResponse(response) {
    if (!response.ok) {
      const error = new Error("HTTP Error")
      error.response = response
      throw error
    }

    return response.json()
  }
}
export default class extends BaseController {
  async load() {
    try {
      const data = await this.handleResponse(
        await fetch("/api/data")
      )
      this.render(data)
    } catch (error) {
      this.handleError(error)
    }
  }
}

Это снижает дублирование и формирует единый стиль обработки HTTP-ошибок.


Интеграция с серверными сообщениями об ошибках

Сервер часто возвращает структурированные ошибки:

{
  "message": "Validation failed",
  "errors": {
    "email": ["Invalid format"],
    "password": ["Too short"]
  }
}

Stimulus-контроллер может трансформировать эти данные в UI-состояние:

async showValidationErrors(response) {
  const data = await response.json()

  Object.entries(data.errors).forEach(([field, messages]) => {
    const input = this.element.querySelector(`[name="${field}"]`)
    input.classList.add("invalid")
    input.nextElementSibling.textContent = messages.join(", ")
  })
}

Такой подход сохраняет сервер как источник истины, а Stimulus — как тонкий слой представления.


Глобальная обработка ошибок через события

Stimulus поддерживает работу с DOM-событиями, что позволяет централизовать обработку:

document.addEventListener("http:error", event => {
  console.error(event.detail)
})

В контроллере:

catch(error) {
  this.dispatch("error", { detail: error })
}

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


Типичные ошибки при реализации

Игнорирование response.ok Приводит к некорректной обработке 4xx и 5xx.

Смешивание логики UI и сетевого слоя Контроллер должен интерпретировать результат, а не содержать бизнес-логику API.

Отсутствие fallback-сценариев Сетевые ошибки должны обрабатываться отдельно от серверных.

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


Архитектурный эффект

Грамотная обработка HTTP-ошибок в Stimulus формирует устойчивый интерфейс, который:

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

Stimulus не скрывает сетевые ошибки за абстракциями, а делает их частью явного кода — именно это и определяет его философию.