Обработка ошибок в JavaScript: try/catch и продвинутые техники в Node.js

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

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

Начать курс

Обработка ошибок в JavaScript: try/catch и не только

Обработка ошибок — одна из ключевых задач при разработке на JavaScript, особенно в среде Node.js, где стабильность серверного приложения напрямую влияет на пользовательский опыт. Необработанные исключения могут привести к падению всего процесса и потере данных. В этой статье мы разберём не только классический блок try/catch, но и продвинутые техники: работу с finally, создание кастомных типов ошибок, обработку асинхронных ошибок и лучшие практики для Node.js.

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

Блок try/catch — фундаментальный механизм перехвата синхронных исключений. Если в блоке try возникает ошибка, выполнение немедленно передаётся в catch. Блок finally выполняется всегда — независимо от того, была ошибка или нет. Это удобно для освобождения ресурсов (закрытие файлов, соединений).

const fs = require('fs');

function readConfig(path) {
  try {
    const data = fs.readFileSync(path, 'utf8');
    console.log('Конфигурация загружена');
    return JSON.parse(data);
  } catch (err) {
    console.error('Ошибка при чтении конфига:', err.message);
    return null;
  } finally {
    console.log('Попытка чтения завершена');
  }
}

const config = readConfig('./config.json');
console.log(config); // null при ошибке

Важно: catch перехватывает только ошибки, возникшие в try. Ошибки в catch или finally остаются необработанными.

2. Типы ошибок в JavaScript и Node.js

В JavaScript существует несколько встроенных типов ошибок, каждый из которых наследуется от Error:

  • SyntaxError — синтаксическая ошибка парсинга кода.
  • ReferenceError — обращение к несуществующей переменной.
  • TypeError — недопустимый тип данных (например, вызов не функции).
  • RangeError — значение вне допустимого диапазона.
  • URIError — ошибка при работе с URI (encodeURI/decodeURI).

В Node.js добавлены AssertionError (из модуля assert) и системные ошибки (EACCES, ENOENT и др.) из модуля fs, которые содержат код ошибки в свойстве code.

try {
  require('fs').readFileSync('/nonexistent');
} catch (err) {
  if (err.code === 'ENOENT') {
    console.log('Файл не найден');
  } else {
    console.log('Другая ошибка:', err.message);
  }
}

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

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

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

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

function saveUser(user) {
  if (!user.email) {
    throw new ValidationError('Email обязателен', 'email');
  }
  // имитация ошибки БД
  throw new DatabaseError('Таймаут соединения', 'INSERT INTO users');
}

try {
  saveUser({ name: 'Иван' });
} catch (err) {
  if (err instanceof ValidationError) {
    console.log(`Поле ${err.field}: ${err.message}`);
  } else if (err instanceof DatabaseError) {
    console.error(`Ошибка БД (${err.query}): ${err.message}`);
    // повторная попытка или логирование
  } else {
    console.error('Неизвестная ошибка:', err);
  }
}

Использование instanceof позволяет строить цепочки обработчиков, не прибегая к проверке строковых кодов.

4. Асинхронные ошибки: Promises и async/await

В Node.js большинство операций асинхронны. Ошибки в промисах обрабатываются через .catch() или блок try/catch с async/await. Важно помнить: try/catch не ловит ошибки в обычных колбэках — только в асинхронных функциях.

// Пример с async/await
const fetch = require('node-fetch');

async function fetchUser(id) {
  try {
    const response = await fetch(`https://api.example.com/users/${id}`);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return await response.json();
  } catch (err) {
    console.error('Ошибка запроса:', err.message);
    throw err; // пробрасываем дальше
  }
}

// Глобальный обработчик необработанных промисов
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  // process.exit(1); // опционально
});

Необработанные отклонённые промисы (unhandledRejection) — одна из частых причин утечек памяти и нестабильности. В Node.js 15+ они приводят к завершению процесса по умолчанию.

5. Лучшие практики для Node.js

  • Не подавляйте ошибки — пустой catch (без логирования) скрывает проблемы.
  • Используйте централизованный обработчик — middleware для Express или глобальный process.on('uncaughtException').
  • Всегда обрабатывайте reject'ы — добавляйте .catch() в конец цепочек промисов.
  • Структурируйте ошибки — используйте кастомные классы и свойство code.
  • Логируйте стек — console.error(err.stack) даёт полный контекст.
  • Не выбрасывайте не-Error объекты — throw 'string' ломает стеки и инструменты.
// Пример централизованного обработчика в Express
function errorHandler(err, req, res, next) {
  console.error(err.stack);
  
  if (err instanceof ValidationError) {
    return res.status(400).json({ error: err.message, field: err                

Блоги

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