Обработка ответов сервера

Stimulus не подменяет собой слой работы с сервером и не навязывает конкретный способ отправки запросов. Он концентрируется на связывании поведения с HTML, а обработка ответов сервера строится поверх стандартных браузерных API: fetch, XMLHttpRequest, FormData, событий DOM и HTTP-статусов. Такой подход позволяет выстраивать предсказуемую архитектуру без скрытой магии.

Контроллер Stimulus обычно отвечает за три задачи:

  • инициирование запроса;
  • реакцию на состояние запроса (загрузка, успех, ошибка);
  • обновление DOM на основе ответа.

Базовая структура контроллера для работы с сервером

Контроллер Stimulus представляет собой класс, методы которого вызываются через действия (data-action). Обработка ответа сервера почти всегда начинается с асинхронного метода.

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  async submit(event) {
    event.preventDefault()

    const response = await fetch("/endpoint")
    const data = await response.json()

    this.handleSuccess(data)
  }

  handleSuccess(data) {
    // обновление интерфейса
  }
}

Ключевые особенности:

  • асинхронность не требует специальных средств от Stimulus;
  • жизненный цикл запроса полностью контролируется разработчиком;
  • логика обработки ответа вынесена в отдельные методы.

Работа с HTTP-статусами

Ответ сервера не всегда означает успешный результат. Stimulus-контроллер должен явно анализировать статус ответа.

const response = await fetch(url)

if (!response.ok) {
  this.handleError(response)
  return
}

Типичные сценарии:

  • 200–299 — успешное выполнение;
  • 400–499 — ошибка клиента (валидация, доступ);
  • 500+ — ошибка сервера.

Рекомендуется разделять обработку:

  • сетевых ошибок (исключения fetch);
  • логических ошибок, возвращённых сервером.
try {
  const response = await fetch(url)

  if (!response.ok) {
    const errorData = await response.json()
    this.showValidationErrors(errorData)
    return
  }

  const data = await response.json()
  this.updateView(data)
} catch (error) {
  this.showNetworkError()
}

Обработка JSON-ответов

JSON — наиболее распространённый формат ответов. Stimulus не добавляет надстроек поверх парсинга, поэтому важно явно контролировать структуру данных.

updateView(data) {
  this.messageTarget.textContent = data.message
}

Рекомендуемые практики:

  • не обращаться к вложенным полям без проверок;
  • разделять бизнес-логику и DOM-манипуляции;
  • не хранить серверные данные в состоянии контроллера без необходимости.

Работа с HTML-ответами

Сервер может возвращать готовые HTML-фрагменты. Это особенно характерно для приложений с server-side rendering.

const html = await response.text()
this.containerTarget.innerHTML = html

При таком подходе:

  • Stimulus управляет моментом вставки;
  • сервер управляет разметкой;
  • повторная инициализация контроллеров происходит автоматически.

Важно учитывать, что замена innerHTML уничтожает старые DOM-узлы и связанные с ними состояния.


Отправка форм и обработка ответов

Stimulus часто используется для перехвата отправки форм.

async submit(event) {
  event.preventDefault()

  const formData = new FormData(this.element)

  const response = await fetch(this.element.action, {
    method: this.element.method,
    body: formData
  })
}

Варианты обработки ответа:

  • JSON с результатами валидации;
  • HTML с перерисованной формой;
  • пустой ответ с кодом 204.

Пример обработки ошибок валидации:

if (response.status === 422) {
  const errors = await response.json()
  this.renderErrors(errors)
}

Состояния загрузки и пользовательский интерфейс

Stimulus-контроллер удобно использовать для управления визуальными состояниями.

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

  try {
    const response = await fetch(this.urlValue)
    const data = await response.json()
    this.render(data)
  } finally {
    this.element.classList.remove("loading")
  }
}

Типичные состояния:

  • загрузка;
  • успешное завершение;
  • ошибка.

Эти состояния выражаются через:

  • CSS-классы;
  • скрытие и показ элементов;
  • изменение текста и атрибутов.

Использование data-атрибутов для параметров ответа

Stimulus позволяет хранить значения, связанные с сервером, в values.

static values = {
  url: String
}

Это позволяет:

  • избегать жёстко прописанных URL;
  • использовать один контроллер в разных контекстах;
  • передавать параметры ответа декларативно.

Обработка ответов без перерисовки DOM

Иногда ответ сервера влияет не на разметку, а на состояние приложения:

  • редиректы;
  • показ уведомлений;
  • изменение глобального состояния.
if (data.redirect) {
  window.location.href = data.redirect
}

Stimulus в таких сценариях выступает как связующее звено между серверной логикой и браузерным поведением.


События как способ реакции на ответ сервера

Ответ сервера может транслироваться в виде кастомного события.

this.element.dispatchEvent(
  new CustomEvent("request:success", { detail: data })
)

Преимущества:

  • слабая связанность компонентов;
  • возможность реакции нескольких контроллеров;
  • упрощение тестирования.

Ошибки и защитное программирование

Контроллер не должен предполагать корректность ответа сервера.

Рекомендуемые меры:

  • try/catch вокруг асинхронного кода;
  • тайм-ауты на запросы;
  • проверки типов данных;
  • резервные ветки обработки.
if (!data || typeof data !== "object") {
  this.showUnexpectedResponse()
}

Взаимодействие с Turbo и другими слоями

При использовании Turbo:

  • сервер может возвращать Turbo Stream;
  • Stimulus не обрабатывает поток напрямую, но реагирует на изменения DOM;
  • ответ сервера может не доходить до контроллера, если Turbo перехватывает навигацию.

В таких случаях контроллер:

  • слушает DOM-события (turbo:submit-end);
  • реагирует на итоговое состояние формы или страницы.

Архитектурные рекомендации

  • Один контроллер — один тип серверного взаимодействия.
  • Логика обработки ответа должна быть детерминированной.
  • Минимум побочных эффектов.
  • Чёткое разделение: запрос → ответ → реакция.

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