Stimulus изначально ориентирован на минимализм и тесную интеграцию с
серверным рендерингом. В отличие от крупных SPA-фреймворков, он не
навязывает собственный слой работы с сетью. HTTP-запросы чаще всего
выполняются через fetch, axios или
вспомогательные библиотеки, а Stimulus-контроллеры отвечают за реакцию
интерфейса на результат этих запросов. Именно поэтому корректная
обработка HTTP-ошибок становится зоной ответственности разработчика и
важной частью архитектуры контроллеров.
HTTP-ошибка — это не только код ответа 4xx или 5xx, но и целый спектр ситуаций: сетевые сбои, таймауты, некорректный JSON, неожиданная структура данных. Stimulus предоставляет инструменты для аккуратного управления состоянием интерфейса в таких сценариях.
Классический сценарий — отправка запроса внутри метода контроллера:
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, поэтому отсутствие явной
проверки приводит к ложному ощущению успешного запроса.
Для контроллеров 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)
}
}
Такой подход позволяет разделить:
Это разделение принципиально важно для корректного UX.
Часто требуется различное поведение для разных диапазонов кодов:
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-контроллер в таком случае выступает как слой интерпретации ответа, а не просто обработчик кликов.
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 не скрывает сетевые ошибки за абстракциями, а делает их частью явного кода — именно это и определяет его философию.