Gernar
Frontend DeveloperJavaScript, React и мониторинг

Интервью-вопрос

Какие знаешь способы отслеживания и обработки ошибок

Нужно показать, что вы умеете не только ловить исключения, но и сохранять нормальный UX, видеть ошибки в production и не отправлять лишние данные в логи. Хороший ответ разделяет локальную обработку, React fallback, глобальный перехват и мониторинг.

Добавлен
Редакция

Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.

🐰0
🥚0

Мини-квиз

Проверка перед разбором

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

Вопрос 1 из 50 правильно

Что ответить, если вас спрашивают про ошибки в fetch?

Вы объясняете, почему простой try/catch вокруг fetch не всегда достаточен.

Варианты ответа

Разбор

Разобраться, а не зазубрить

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

Базовая идея

В ответе лучше сразу разделить два слова: обработка и отслеживание. Обработка отвечает за то, что увидит пользователь прямо сейчас. Отслеживание отвечает за то, как команда узнает об ошибке после релиза.

Коротко можно сказать так:

Я стараюсь ловить ошибку как можно ближе к месту, где могу принять правильное решение. Если это запрос, обрабатываю статус и сетевые ошибки. Если это React-рендер, использую Error Boundary для fallback UI. Если ошибка не была поймана локально, глобальные обработчики и monitoring помогают не потерять сигнал в production.

Такой ответ показывает систему, а не просто список инструментов. Это звучит сильнее, чем перечисление try/catch, console.error и Sentry без контекста.

Уровни обработки

На практике фронтенд обычно использует несколько уровней защиты. Локальный уровень нужен там, где понятен сценарий: форма, загрузка списка, сохранение настроек, оплата. В этих местах вы можете показать сообщение, не потерять состояние и дать повторить действие.

Уровень UI нужен, чтобы приложение не оставалось в вечном loading или пустом экране. Для React это состояние ошибки в компоненте, fallback, Error Boundary для части дерева и аккуратный rollback при optimistic update.

Глобальный уровень нужен как последняя сетка безопасности. Он не делает сценарий удобным сам по себе, зато помогает заметить в production необработанные исключения, ошибки загрузки ресурсов или promise без catch.

Как выбрать место обработки ошибки

1Ошибка ожидаемая, например валидация формы или 404?
Обработайте ее как часть сценария: покажите сообщение, подсветите поле, дайте retry или альтернативное действие.
2Ошибка случилась в запросе или async-функции?
Используйте try/catch или .catch, проверяйте response.ok, обновляйте состояние error и очищайте loading.
3Падает часть React-дерева при рендере?
Поставьте Error Boundary вокруг рискованной зоны, покажите fallback и отправьте детали в мониторинг.
4Ошибка не была обработана локально?
Ловите ее глобально через error и unhandledrejection, чтобы не потерять сигнал в production.
5Ошибка важна для команды после релиза?
Отправляйте ее в Sentry, Rollbar или свой collector с release, route, breadcrumbs и безопасным контекстом.

Пример с запросом к API

Частая ловушка на интервью связана с fetch. Он делает reject при сетевой ошибке, отмене запроса или похожей проблеме транспорта. Но HTTP-ответ со статусом 404 или 500 обычно придет как обычный response, поэтому его нужно проверить явно.

Плохой пример:

async function loadProfile() {
  const response = await fetch("/api/profile");
  return response.json();
}

Здесь ошибка сервера может попасть в код как будто это успешный ответ. Еще хуже, если UI уже очистил старые данные и остался пустым. Если такой вызов стоит в React-эффекте и параметр быстро меняется, старый ответ может перетереть новые данные.

Более безопасный вариант для функции загрузки:

async function loadProfile(profileId, { signal } = {}) {
  const response = await fetch(`/api/profile/${encodeURIComponent(profileId)}`, { signal });

  if (!response.ok) {
    throw new Error(`Profile request failed: ${response.status}`);
  }

  return response.json();
}

async function showProfile(currentProfileId) {
  setStatus("loading");

  try {
    const profile = await loadProfile(currentProfileId);
    setProfile(profile);
    setStatus("success");
  } catch (error) {
    setStatus("error");
    reportError(error, { route: "/profile" });
  }
}

В компоненте с загрузкой по параметру добавьте отмену. Это защищает от race condition и обновления состояния после размонтирования.

useEffect(() => {
  const controller = new AbortController();

  setStatus("loading");

  loadProfile(profileId, { signal: controller.signal })
    .then((profile) => {
      setProfile(profile);
      setStatus("success");
    })
    .catch((error) => {
      if (error.name === "AbortError") return;

      setStatus("error");
      reportError(error, { route: "/profile" });
    });

  return () => controller.abort();
}, [profileId]);

Ошибку в UI лучше делать не только видимой, но и доступной. Например, сообщение о неудачной загрузке можно отдать через role="alert" или связать с полем формы через aria-describedby. Тогда пользователь клавиатуры или скринридера тоже поймет, что произошло.

Безопасный flow запроса
  1. 1Показать loading и сохранить прежнее состояние UI
  2. 2Сделать запрос и проверить response.ok
  3. 3На ошибке показать понятное сообщение, доступное для скринридера, и retry
  4. 4Залогировать unexpected error с контекстом
  5. 5Всегда сбросить loading в finally
Опасный flow запроса
  1. 1Считать любой ответ успешным
  2. 2Сразу перетереть данные в UI
  3. 3Оставить пользователя без сообщения об ошибке
  4. 4Писать только console.error в production
  5. 5Забыть вернуть loading в нормальное состояние

React и Error Boundary

Error Boundary стоит упоминать аккуратно. Он полезен, когда часть интерфейса может упасть при рендере, но вы хотите сохранить остальное приложение живым. Например, можно обернуть виджет с графиком, сложный кабинет или область с данными от внешнего сервиса.

Минимальный пример:

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    reportError(error, { componentStack: errorInfo.componentStack });
  }

  render() {
    if (this.state.hasError) {
      return <p>Не удалось показать этот блок. Попробуйте обновить страницу.</p>;
    }

    return this.props.children;
  }
}

Важно сразу сказать ограничение. Error Boundary не ловит ошибку внутри обработчика клика, setTimeout или async-функции. Там нужна обычная обработка в самом сценарии.

Глобальное отслеживание

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

window.addEventListener(
  "error",
  (event) => {
    const target = event.target;
    const isResourceError = target && target !== window;

    reportError(event.error ?? new Error(String(event.message || "Resource load error")), {
      type: isResourceError ? "resource-error" : "window-error",
      source: isResourceError ? target.src || target.href : event.filename,
      line: event.lineno,
    });
  },
  true
);

window.addEventListener("unhandledrejection", (event) => {
  reportError(event.reason, {
    type: "unhandledrejection",
  });
});

Третий аргумент true нужен, если вы хотите поймать ошибки загрузки скриптов, картинок или CSS в фазе capture. Но даже такой код не чинит экран пользователя. Он только отправляет сигнал команде.

В продакшене важно добавить release, sourcemaps, route, breadcrumbs и безопасный user id. Без версии релиза и sourcemaps stack trace из минифицированного бандла часто мало помогает. Но не отправляйте токены, cookies, пароли, содержимое форм и полный payload запроса.

Что сказать на интервью

Хороший ответ можно собрать в 4 шага.

  1. Сначала назвать локальную обработку: try/catch, .catch, проверка статуса ответа, состояние ошибки в UI.
  2. Затем React-уровень: Error Boundary для ошибок рендера и fallback UI.
  3. Потом глобальный уровень: error и unhandledrejection как последняя сетка безопасности.
  4. В конце monitoring: Sentry, Rollbar или свой collector с фильтрацией данных и контекстом релиза.

Если хотите звучать практично, добавьте мысль про пользователя. Ошибка должна быть не только записана в лог, но и превращена в понятное состояние интерфейса. Это сразу отделяет зрелый frontend-подход от простого перечисления API.

Частые ошибки

Где обычно ошибаются

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

  1. 1

    Подменять обработку ошибки логированием

    console.error или отправка события в Sentry не помогают пользователю продолжить сценарий. Нужен UI-ответ: fallback, сообщение, повтор действия, сохранение введенных данных или откат optimistic update. На интервью лучше сказать, что логирование и UX-обработка решают разные задачи.
  2. 2

    Ждать, что fetch бросит ошибку на 500

    fetch не делает reject для обычных HTTP-ошибок. Если не проверить response.ok, можно показать пользователю некорректные данные или сломать парсинг ответа. Безопаснее явно обрабатывать статус и отдельно ловить сетевые ошибки.
  3. 3

    Использовать Error Boundary как универсальную защиту

    Error Boundary не ловит ошибки в setTimeout, обработчиках клика и async-функциях. Если сказать, что он ловит все, это легко проверят уточняющим вопросом. Правильнее описывать его как защиту от ошибок рендера внутри части React-дерева.
  4. 4

    Отправлять в мониторинг лишние данные

    В error-report часто случайно попадают токены, email, содержимое форм и полный request body. Это риск безопасности и приватности. Перед отправкой нужно фильтровать данные и оставлять только контекст, который помогает воспроизвести проблему.
  5. 5

    Не различать expected и unexpected ошибки

    Неверный пароль, пустая корзина или 404 для удаленной сущности часто являются ожидаемым состоянием продукта. TypeError в рендере или падение парсинга JSON уже ближе к дефекту. Если все отправлять как критические ошибки, мониторинг быстро превращается в шум.
  6. 6

    Забывать отмену запроса при смене экрана

    Если пользователь быстро меняет фильтр, карточку или маршрут, старый ответ может прийти позже нового и перетереть актуальный UI. В React это часто чинят через AbortController, cleanup в useEffect или проверку актуального request id.

Follow-up

Что могут спросить дальше

Короткие ответы на вопросы, которыми проверяют практическое понимание обработки ошибок во фронтенде.

Живые ответы

Видео с похожим вопросом

Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.

Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.

Содержание