EvolCode
треки/С нуля·11 / 11
6 мин чтения

Ошибки — try/catch без боли

Ошибки — это не «когда что-то плохо», а нормальная часть программы. Файл может не открыться, сервер вернёт 500, юзер ввёл фигню. Уметь их ловить и обрабатывать — отдельный навык, который ИИ часто реализует с перекосами (перестраховка ИЛИ полное игнорирование).

throw — кидаем ошибку

throw new Error("сообщение") — выбрасывает ошибку. После throw код функции ОБРЫВАЕТСЯ — то что после, не выполнится. Ошибка «всплывает» вверх по стеку вызовов пока кто-то её не поймает (try/catch). Если до самого верха никто не поймал — программа падает с ошибкой в консоль.

try/catch — ловим

$ try { ... } catch (e) { ... }

В try-блоке выполняется код, в catch — что делать если случилась ошибка. e (или err) — объект ошибки с полями message (текст), name (тип), stack (где случилось). После catch программа продолжается дальше, не падает. Опционально finally { ... } — выполнится ВСЕГДА, успех или ошибка.

Главное правило: НЕ съедать

Худший паттерн: try { ... } catch {} — поймали и забыли. Ошибка случается, но вы об этом не узнаете, и баг живёт в продакшне неделями. Минимум: console.error(e). Лучше: показать пользователю что-то осмысленное («не удалось загрузить, проверьте интернет»). ИИ часто делает «защитный» try/catch вокруг всего — в большинстве случаев это лишнее.

Когда кидать самому

Когда программа дальше точно не сможет работать осмысленно. Пример: функция считает скидку, а массив товаров пустой —

$ throw new Error("Список товаров пустой")

лучше чем «вернуть 0» (и пользователь увидит «скидка 0% — почему?»). Кидать ошибку — это передать решение «как с этим жить» на уровень выше.

Свои типы ошибок

Иногда хочется отличать «сетевую» ошибку от «валидации». Тогда:

$ class NetworkError extends Error {}

потом ловишь и проверяешь

$ if (e instanceof NetworkError) { ... }

Это уже продвинуто, но видишь часто в TypeScript-проектах. ИИ любит делать кучу классов ошибок — оцени, нужно ли тебе их столько.

примерjavascript
// === БАЗОВЫЙ try/catch ===
async function loadUser(id) {
  try {
    const r = await fetch(`/api/users/${id}`);
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    return await r.json();
  } catch (e) {
    console.error('Не загрузили пользователя:', e.message);
    return null;            // явный fallback вместо падения
  }
}

// === ПЛОХО — съели ошибку ===
async function badLoad() {
  try {
    return await fetch('/api/x').then(r => r.json());
  } catch {
    // молча. Что-то пошло не так — никто не узнает.
  }
}

// === ХОРОШО — конкретная обработка по типу ===
async function smartLoad(id) {
  try {
    const r = await fetch(`/api/users/${id}`);
    if (r.status === 404) {
      return { notFound: true };          // не ошибка, штатный кейс
    }
    if (!r.ok) {
      throw new Error(`Сервер вернул ${r.status}`);
    }
    return await r.json();
  } catch (e) {
    if (e.name === 'TypeError') {
      // fetch кидает TypeError при отсутствии сети
      return { error: 'Нет интернета' };
    }
    return { error: e.message };
  }
}

// === КОГДА КИДАТЬ САМОМУ ===
function calcDiscount(items, percent) {
  if (!items || items.length === 0) {
    throw new Error('Нет товаров для расчёта');
  }
  if (percent < 0 || percent > 100) {
    throw new Error('Процент должен быть 0-100');
  }
  const total = items.reduce((s, i) => s + i.price, 0);
  return total * (1 - percent / 100);
}

// Использующий код решает что делать с ошибкой:
try {
  const sum = calcDiscount(cart, 15);
  showResult(sum);
} catch (e) {
  showToast('Не получилось: ' + e.message);
}

// === finally — всегда выполнится ===
async function withSpinner(fn) {
  showSpinner(true);
  try {
    return await fn();
  } finally {
    showSpinner(false);   // спиннер скрыли при успехе И при ошибке
  }
}

Хорошие и плохие паттерны обработки ошибок

главное
  • 01throw new Error(...) — выбрасывает ошибку, код после throw не выполняется
  • 02try/catch ловит ошибку, программа продолжается без падения
  • 03"Съесть" ошибку (catch без действий) — главный антипаттерн
  • 04Минимум — console.error, лучше — понятное сообщение пользователю
  • 05finally — выполнится всегда (закрыть соединение, скрыть спиннер)
  • 06Кидать свою ошибку оправдано когда дальше работать осмысленно нельзя
  • 07fetch не считает 4xx/5xx ошибкой — нужно вручную проверять r.ok
проверь себя
01fetch получил 404. Попадёт ли управление в catch?
02Что делает finally?
03Почему `try { ... } catch {}` — антипаттерн?
← к треку