Обработка ошибок в JavaScript: try/catch и не только — полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему обработка ошибок — это фундамент надежного кода

Ошибки в коде неизбежны. Они возникают из-за некорректных данных пользователя, сбоев сети, отсутствия файлов на сервере или банальных опечаток разработчика. В среде Node.js, где код выполняется асинхронно и часто взаимодействует с внешними ресурсами (базы данных, файловая система, API), необработанная ошибка может привести к падению всего процесса. Грамотная обработка ошибок в JavaScript превращает хрупкое приложение в устойчивый сервис. В этой статье мы разберем все ключевые механизмы: от классического try/catch до создания собственных классов ошибок и работы с асинхронным кодом.

1. Основы: try, catch, finally и throw

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

Синтаксис и работа try/catch

Код, который может выбросить исключение, помещается в блок try. Если ошибка возникает, управление мгновенно передается в блок catch, где доступен объект ошибки. Если ошибки нет, блок catch игнорируется.

try {
  const data = JSON.parse('{"name": "Анна"}'); // Корректный JSON
  console.log(data.name); // Выведет: Анна
} catch (error) {
  console.error('Ошибка парсинга:', error.message);
}
// Выполнение продолжается

Теперь смоделируем ошибку:

try {
  const data = JSON.parse('невалидный json'); // Ошибка!
  console.log(data.name);
} catch (error) {
  console.error('Ошибка парсинга:', error.message);
  // Вывод: Ошибка парсинга: Unexpected token 'н', ... is not valid JSON
}
console.log('Программа жива!'); // Этот код выполнится

Блок finally: код, который выполнится всегда

Блок finally выполняется независимо от того, была ли ошибка или нет. Он идеально подходит для освобождения ресурсов (закрытие файлов, соединений с БД) или очистки таймеров.

let resource = null;
try {
  resource = acquireResource(); // Получаем ресурс
  resource.doSomething(); // Опасная операция
} catch (error) {
  console.error('Ошибка:', error);
} finally {
  if (resource) {
    resource.close(); // Всегда закрываем ресурс
    console.log('Ресурс освобожден.');
  }
}

Оператор throw: создаем свои ошибки

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

function divide(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new Error('Аргументы должны быть числами');
  }
  if (b === 0) {
    throw new Error('Деление на ноль запрещено');
  }
  return a / b;
}

try {
  console.log(divide(10, 0));
} catch (error) {
  console.error('Ошибка:', error.message); // Ошибка: Деление на ноль запрещено
}

2. Продвинутые техники: Собственные классы ошибок и типизация

В больших проектах просто использовать стандартный Error недостаточно. Нужно различать ошибки сети, валидации, авторизации и другие. Для этого создают пользовательские классы, расширяющие встроенный Error.

Создание кастомных ошибок

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'ValidationError'; // Уникальное имя для проверки
  }
}

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

function fetchUser(id) {
  if (!id) {
    throw new ValidationError('ID пользователя обязателен');
  }
  // Имитация сетевого запроса
  if (id < 0) {
    throw new NetworkError('Сервер недоступен', 500);
  }
  return { id, name: 'Иван' };
}

try {
  fetchUser(-1);
} catch (error) {
  if (error instanceof ValidationError) {
    console.log('Ошибка валидации:', error.message);
  } else if (error instanceof NetworkError) {
    console.log(`Сетевая ошибка (${error.statusCode}): ${error.message}`);
  } else {
    console.log('Неизвестная ошибка:', error);
  }
}

Проверка типа ошибки

Вместо instanceof иногда используют свойство name или код ошибки. instanceof предпочтительнее, так как он работает с иерархией классов.

3. Асинхронные ошибки: Callbacks, Promises и Async/Await

Node.js построен на асинхронности. Ошибки в асинхронном коде не могут быть пойманы try/catch на том же уровне, что и синхронный код. Рассмотрим эволюцию подходов.

Эпоха Callbacks (Node.js-style)

В старом стиле первым аргументом коллбэка всегда передавалась ошибка (error-first callback).

const fs = require('fs');

fs.readFile('/path/to/file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('Не удалось прочитать файл:', err.message);
    return;
  }
  console.log(data);
});

Promises: .catch()

Промисы позволяют обрабатывать ошибки цепочкой .catch().

const fs = require('fs').promises;

fs.readFile('/path/to/file.txt', 'utf8')
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('Ошибка при чтении файла:', error.message);
  });

Async/Await: try/catch для асинхронного кода

Синтаксис async/await вернул возможность использовать привычный try/catch для асинхронных операций. Это самый читаемый и современный способ.

const fs = require('fs').promises;

async function readConfig() {
  try {
    const data = await fs.readFile('config.json', 'utf8');
    const config = JSON.parse(data);
    console.log('                

Блоги

Рекомендаций по книгам