Kodokon kodokon.com

Вызов API: fetch, загрузка и ошибки

Загружай удалённые данные через fetch внутри эффекта, аккуратно моделируя состояния загрузки и ошибки.

10 мин · 3 вопросов

Открыть этот урок в Kodokon

Сетевой запрос занимает время и может провалиться: твой интерфейс должен отражать эту реальность. Профессиональное соглашение - отслеживать три отдельных состояния: полученные данные, булев флаг загрузки и возможную ошибку. Сам запрос идёт внутрь useEffect, потому что обращение к серверу - это побочный эффект.

JSX
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
  fetch("https://api.example.com/users")
    .then((res) => {
      if (!res.ok) throw new Error("HTTP " + res.status);
      return res.json();
    })
    .then((data) => setUsers(data))
    .catch((err) => setError(err.message))
    .finally(() => setLoading(false));
}, []);
Три состояния на один запрос: данные, загрузка, ошибка.

В рендере обрабатывай случаи по порядку: сначала загрузка, затем ошибка и в конце данные. Эти ранние возвраты сохраняют код читаемым и гарантируют, что пользователь всегда видит что-то осмысленное: индикатор ожидания, сообщение о сбое или ожидаемый список.

JSX
if (loading) return <p>Loading…</p>;
if (error) return <p>Error: {error}</p>;

return (
  <ul>
    {users.map((user) => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);
Ранние возвраты: одновременно показывается только один случай.

Последняя ловушка: если зависимость меняется (например, userId), уходит новый запрос, тогда как старый может быть ещё в пути. Если старый ответит последним, он перезапишет свежие данные. Классическое противоядие: флаг ignore, который функция очистки ставит в true, чтобы выбрасывать устаревшие ответы.

JSX
useEffect(() => {
  let ignore = false;
  setLoading(true);

  fetch("/api/users/" + userId)
    .then((res) => res.json())
    .then((data) => {
      if (!ignore) setUser(data);
    })
    .finally(() => {
      if (!ignore) setLoading(false);
    });

  return () => {
    ignore = true;
  };
}, [userId]);
Каждый устаревший ответ отбрасывается благодаря флагу.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Зачем отслеживать три состояния (данные, загрузка, ошибка) для запроса?
    • Потому что fetch требует эти три переменные для работы
    • Чтобы интерфейс отражал каждую фазу: ожидание, сбой или успех
    • Чтобы React не запускал запрос заново при каждом рендере
  2. Твой API отвечает статусом 500, но catch так и не срабатывает. Почему?
    • fetch отклоняет промис только при сетевом сбое, а не при HTTP-статусе ошибки
    • catch не работает внутри useEffect
    • Сервер должен прислать специальный заголовок, чтобы сработал catch
  3. Для чего нужен флаг ignore, который очистка эффекта ставит в true?
    • Чтобы отменить выполняющийся HTTP-запрос на стороне сервера
    • Чтобы React не перезапускал эффект
    • Чтобы отбросить ответ запроса, который стал неактуальным