ДокументацияJavaScriptОбработка ошибок в JavaScript: try/catch/finally, Error, кастомные ошибки
Средний 8 мин чтения

Обработка ошибок в JavaScript: try/catch/finally, Error, кастомные ошибки

Обработка ошибок в JavaScript — try...catch...finally, объект Error, создание кастомных ошибок, Promise.catch, unhandledrejection и лучшие практики.

try catchErrorошибкаисключениеerror handlingthrowfinallyJavaScript

try...catch

Оборачивает код, который может выбросить ошибку. Если в try что-то пошло не так — управление переходит в catch:

try {
  const data = JSON.parse('не json')
} catch (error) {
  console.log('Ошибка парсинга:', error.message) // 'Ошибка парсинга: Unexpected token...'
}

Без try/catch скрипт остановился бы на строке с JSON.parse.

Блок finally

Выполняется всегда — была ошибка или нет:

try {
  const response = await fetch('/api/users')
  const data = await response.json()
} catch (error) {
  console.log('Запрос не удался:', error.message)
} finally {
  hideLoadingSpinner() // выполнится в любом случае
}

Типичный сценарий — очистка ресурсов (спиннер, индикатор загрузки, закрытие соединения).

function readConfig() {
  let file

  try {
    file = openFile('config.json')
    return file.read()
  } catch (error) {
    return { default: true }
  } finally {
    if (file) file.close() // закроется при любом исходе
  }
}

Объект Error

У ошибки есть свойства:

try {
  undefinedFunction()
} catch (error) {
  console.log(error.name)    // 'ReferenceError'
  console.log(error.message) // 'undefinedFunction is not defined'
  console.log(error.stack)   // стек вызовов (строка за строкой)
}

Типы встроенных ошибок

ТипКогда возникает
ReferenceErrorобращение к несуществующей переменной
TypeErrorоперация с неверным типом (null.x)
SyntaxErrorнекорректный синтаксис (обычно при парсинге)
RangeErrorзначение вне допустимого диапазона
URIErrorнекорректный encodeURI / decodeURI
null.property   // TypeError: Cannot read properties of null
x               // ReferenceError: x is not defined (в строгом режиме)
new Array(-1)   // RangeError: Invalid array length

throw — выбросить ошибку вручную

function divide(a, b) {
  if (b === 0) {
    throw new Error('Деление на ноль')
  }
  return a / b
}

try {
  divide(10, 0)
} catch (e) {
  console.log(e.message) // 'Деление на ноль'
}

throw работает с чем угодно, но принято выбрасывать объекты Error:

throw new Error('Что-то пошло не так')
throw new TypeError('Ожидалось число')
throw new RangeError('Значение от 1 до 10')

// Можно, но не нужно
throw 'строка'    // работает, но нет stack trace
throw { code: 404 } // работает, но теряете информацию

Кастомные ошибки

Создайте свой класс, наследуясь от Error:

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

class NotFoundError extends Error {
  constructor(resource) {
    super(`${resource} не найден`)
    this.name = 'NotFoundError'
    this.resource = resource
  }
}

Использование:

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

try {
  validateAge('двадцать')
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(`Поле "${error.field}": ${error.message}`)
    // Поле "age": Возраст должен быть числом
  } else {
    throw error // неизвестная ошибка — пробрасываем дальше
  }
}

instanceof — правильный способ проверить тип ошибки. Не проверяйте через error.name — имя может не совпасть после минификации.

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

try/catch не ловит ошибки внутри колбэков

try {
  setTimeout(() => {
    throw new Error('Ошибка в таймере') // try/catch её не поймает
  }, 100)
} catch (e) {
  // Сюда не попадём — try уже завершился
}

Promise: .catch()

fetch('/api/users')
  .then(response => {
    if (!response.ok) throw new Error(`HTTP ${response.status}`)
    return response.json()
  })
  .then(data => console.log(data))
  .catch(error => console.log('Ошибка:', error.message))

async/await: try/catch

async function loadUsers() {
  try {
    const response = await fetch('/api/users')
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`)
    }
    const users = await response.json()
    return users
  } catch (error) {
    console.log('Не удалось загрузить пользователей:', error.message)
    return []
  }
}

Глобальная обработка ошибок

В браузере

// Синхронные ошибки
window.addEventListener('error', (event) => {
  console.log('Глобальная ошибка:', event.message)
})

// Неперехваченные Promise-ошибки
window.addEventListener('unhandledrejection', (event) => {
  console.log('Unhandled rejection:', event.reason)
  event.preventDefault() // предотвратить вывод в консоль
})

В Node.js

process.on('uncaughtException', (error) => {
  console.error('Uncaught:', error)
  process.exit(1)
})

process.on('unhandledRejection', (reason) => {
  console.error('Unhandled rejection:', reason)
})

Лучшие практики

Ловите конкретные ошибки, а не все подряд:

// Плохо — глотает любые ошибки
try {
  saveUser(user)
  sendEmail(user.email)
} catch (e) {}

// Лучше — обрабатываем только то, что ожидаем
try {
  saveUser(user)
} catch (error) {
  if (error instanceof ValidationError) {
    showFieldError(error.field, error.message)
  } else {
    throw error // пробрасываем неизвестную ошибку
  }
}

Не глотайте ошибки молча:

// Плохо
try {
  doSomething()
} catch (e) {
  // тишина
}

// Лучше — хотя бы логируйте
try {
  doSomething()
} catch (e) {
  console.error('doSomething failed:', e)
}

Используйте finally для очистки:

showLoading()
try {
  await fetchData()
} catch (e) {
  showError(e.message)
} finally {
  hideLoading() // гарантирует, что спиннер исчезнет
}

Итог

  • try/catch/finally — основной механизм обработки ошибок
  • В catch проверяйте тип ошибки через instanceof
  • Выбрасывайте new Error() или свои классы-наследники — не строки
  • В async/await используйте try/catch, в промисах — .catch()
  • Не глотайте ошибки молча — хотя бы логируйте