JS обработка ошибок

Обработка ошибок в JavaScript — механизм перехвата и управления исключениями с помощью конструкций try...catch...finally, объекта Error и его подклассов.

Зачем нужно

Без обработки ошибок любое исключение прерывает выполнение программы и ломает пользовательский интерфейс. Правильная обработка позволяет graceful degradation — показать понятное сообщение, откатить операцию, залогировать проблему. В асинхронном коде (Promise, async/await) необработанные ошибки могут теряться незаметно, что делает понимание этого механизма критически важным.

Где используется

  • Обработка ответов API: сетевые ошибки, статусы 4xx/5xx
  • Валидация входных данных: бросать ошибки при некорректном вводе
  • Работа с localStorage, IndexedDB, File API — могут выбросить SecurityError
  • Асинхронные операции: .catch в Promise-цепочках, try/catch в async-функциях
  • Глобальный перехват: window.onerror, window.onunhandledrejection для мониторинга

Основной контент

try...catch...finally

function parseJSON(str) {
  try {
    return JSON.parse(str); // может выбросить SyntaxError
  } catch (err) {
    console.error('Ошибка парсинга:', err.message);
    return null;
  } finally {
    console.log('parseJSON завершён'); // выполняется всегда
  }
}

parseJSON('{"name":"Иван"}'); // успех
parseJSON('не JSON');          // ошибка перехвачена, возвращает null

Объект Error и его свойства

try {
  null.property; // TypeError
} catch (err) {
  console.log(err instanceof TypeError); // true
  console.log(err.name);    // "TypeError"
  console.log(err.message); // "Cannot read properties of null"
  console.log(err.stack);   // стек вызовов
}

// Встроенные подтипы Error
// TypeError    — неверный тип
// RangeError   — значение вне допустимого диапазона
// ReferenceError — обращение к необъявленной переменной
// SyntaxError  — ошибка синтаксиса
// URIError     — некорректный URI

Пользовательские ошибки

class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

class NetworkError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.name = 'NetworkError';
    this.statusCode = statusCode;
  }
}

function validateAge(age) {
  if (typeof age !== 'number') {
    throw new ValidationError('Возраст должен быть числом', 'age');
  }
  if (age < 0 || age > 150) {
    throw new ValidationError('Недопустимый возраст', 'age');
  }
  return age;
}

try {
  validateAge('abc');
} catch (err) {
  if (err instanceof ValidationError) {
    console.log(`Ошибка поля ${err.field}: ${err.message}`);
  } else {
    throw err; // перебрасываем неизвестные ошибки
  }
}

Ошибки в асинхронном коде

// Promise — .catch
fetch('/api/data')
  .then(res => {
    if (!res.ok) throw new NetworkError('Ошибка сети', res.status);
    return res.json();
  })
  .catch(err => {
    console.error('Запрос провалился:', err.message);
  });

// async/await — try/catch
async function loadUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) throw new NetworkError('Не найден', res.status);
    return await res.json();
  } catch (err) {
    if (err instanceof NetworkError && err.statusCode === 404) {
      return null; // Обработали 404
    }
    throw err; // остальное пробрасываем
  }
}

Глобальный перехват

// Синхронные ошибки
window.onerror = (message, source, lineno, colno, error) => {
  console.error('Глобальная ошибка:', message, error);
  return true; // предотвращает вывод в консоль браузера
};

// Необработанные Promise-отказы
window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled rejection:', event.reason);
  event.preventDefault();
});

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

  • Проглатывание ошибок: пустой catch без логирования скрывает проблемы навсегда.
  • Catch ловит всё: если вы ловите Error, но обрабатываете только один тип — перебрасывайте остальные через if (!(err instanceof MyError)) throw err.
  • Потеря ошибки в async: async function foo() { somePromise; } — промис без await не перехватывается try/catch внутри функции.
  • finally возвращает значение: return в finally перекрывает return в try — редко нужно, всегда удивляет.

Связанные темы

Ресурсы