Как управлять состоянием загрузки и ошибками при работе с API?
Что проверяют
Умеет ли кандидат довести асинхронный запрос до предсказуемого состояния интерфейса: не только показать спиннер, но и обработать HTTP-ошибку, гонку ответов и повтор. Отдельно проверяют, знает ли человек, что fetch не отклоняет промис на 404 и 500.
Как отвечать
Минимум три состояния на запрос: loading, data, error. Ошибку хранить объектом или строкой, а не булевым флагом: пользователю надо показать причину и дать кнопку повтора.
Главный технический момент про fetch: промис отклоняется только на сетевом сбое, отмене или блокировке CORS. Ответ 404 или 500 приходит как успешный промис с ok === false, поэтому проверка обязательна:
const res = await fetch(url, { signal });
if (!res.ok) throw new HttpError(res.status, await res.text());
const data = await res.json();axios ведёт себя иначе, он сам отклоняет промис на 4xx/5xx, так что переезд с одной библиотеки на другую ломает обработку ошибок.
Сбрасывать ошибку перед новым запросом и снимать флаг загрузки в finally, иначе спиннер зависнет навсегда при исключении.
Гонка ответов: при быстром переключении фильтров ответ старого запроса может прийти после нового и затереть данные. Лечится AbortController в cleanup эффекта или сравнением идентификатора запроса перед записью в состояние.
useEffect(() => {
const ac = new AbortController();
load(query, ac.signal);
return () => ac.abort();
}, [query]);AbortError при этом не ошибка пользователя, показывать его не нужно.
На реальных проектах это редко пишут руками. TanStack Query, RTK Query или SWR дают isPending/isError/error, дедупликацию одинаковых запросов, кэш, повторы с экспоненциальной паузой и инвалидацию после мутации. Ручной useState оправдан для одного изолированного запроса.
Глобальное против локального: ошибку показывают рядом с блоком, который её вызвал. Глобально имеет смысл выносить только сквозные случаи: 401 с редиректом на логин и падение всего экрана через error boundary. Держать в Redux флаг загрузки каждой формы это лишний слой без выгоды.
По UX: скелетон вместо спиннера на первой загрузке, сохранение прежних данных при фоновом обновлении, отдельная подача сетевой ошибки и ошибки валидации, кнопка «Повторить».
Где спотыкаются
Оборачивают fetch в try/catch без проверки response.ok, парсят тело ошибки как данные и показывают пустой успешный экран. Второй частый провал: не упоминают гонку ответов и отмену запроса при смене параметров.