Исключения в JavaScript: обработка ошибок с помощью try, catch, throw, finally
Ловим и обрабатываем ошибки с использованием JavaScript-инструментов.


При работе веб-сайта в реальном времени могут возникать ошибки — например, при проверке, заполнил ли пользователь все поля для регистрации, или при обращении веб-сервиса к серверу. Эти ошибки называются исключениями в JavaScript. Можно поймать исключения и предотвратить крах программы.
Сегодня вы узнаете, что такое исключения в JavaScript, какие виды ошибок и исключений бывают и как они обрабатываются с помощью механизма try…catch, а также напишете своё первое исключение на JavaScript.
Содержание
- Что такое исключения и ошибки в JavaScript
- Какие бывают ошибки
- Конструкция try-catch в исключениях JavaScript
- Блок finally в исключениях JavaScript
- Объект Error и его свойства в JavaScript
- Выброс исключения throw в JavaScript
- Как правильно обрабатывать асинхронные ошибки в JavaScript
- Практика: проектируем систему ошибок в приложении JavaScript
- Частые ошибки при работе с исключениями в JavaScript
- Когда использовать исключения
- Когда можно обойтись без исключений
- Что ещё почитать
Что такое исключения и ошибки в JavaScript
Исключения (exceptions) в JavaScript — это ошибки в процессе компиляции или выполнении кода. Например, пользователь не заполнил на сайте обязательное поле «Имя» и нажал кнопку «Зарегистрироваться» — случилось исключение.
Создатели JS предусмотрели такие случаи. Чтобы программа не вылетала после ошибки, в коде используют специальные языковые конструкции — try, catch, finally. Ещё это называют обработкой исключений. Если не обработать исключение, скрипт может сломаться и дальнейшая работа сервиса станет непредсказуемой.
try {
// Код, где может произойти ошибка
} catch (error) {
// Выполнится, если ошибка произошла
} finally {
// Дополнительный блок
// Выполнится после всех событий
}
Какие бывают ошибки
В JavaScript бывают разные ошибки, и за каждый вид отвечает определённый класс:
- SyntaxError — синтаксическая ошибка (например, забыта скобка).
// Пропущены закрывающие скобки
eval("function test() { console.log('Hello'");
- ReferenceError — ошибка ссылки (обращение к несуществующей переменной — адрес ссылки, которого нет).
// Обращение к несуществующей переменной — адрес ссылки, которого нет)
console.log(nonExistentVariable);
- TypeError — ошибка типа (неверный тип данных).
// Попытка вызвать число как функцию
let num = 16;
num();
- RangeError — ошибка диапазона (обращение к несуществующему индексу массива).
// Отрицательная длина массива
let arr = new Array(-5);
- URIError — возникает при работе функций encodeURI, decodeURI, если они получают неверный аргумент.
// Символ % без кода — неправильный URI
decodeURIComponent('%');
- EvalError — возникает при некорректной работе eval(), функции, которая берёт строку кода и выполняет её.
// Искусственно создаём EvalError
throw new EvalError("Некорректное использование eval()");
- AggregateError — возникает, когда несколько ошибок происходит одновременно.
// Ждёт первый успешный промис
Promise.any([
Promise.reject("Ошибка A"),
Promise.reject("Ошибка B")
])
.catch(e => console.log(e));
Конструкция try-catch в исключениях JavaScript
В блоке try пишется код, в котором может возникнуть исключение, а в catch пишется код, который выполнится, если какая-то ошибка всё же произошла. Оба элемента пишутся в фигурных скобках. Они зависят друг от друга и не могут существовать по отдельности.
try {
// Код, который может вызвать ошибку
} catch (error) {
// Код, который выполняется при появлении ошибки в блоке try
console.error("Ошибка: ", error.message);
}
Если запустить этот код без try…catch, вылетит исключение.
start(); // Такой функции нет
console.log("Привет"); // Не выполнится
Весь код JavaScript содержит только эти две строки. Никакого метода start() в JavaScript не существует, и в консоли после выполнения этой строки вы увидите Uncaught ReferenceError: start is not defined, а строчка с сообщением «Привет» и весь дальнейший код не будут выполнены.

Обновим код, используя try и catch. В try положим код, который может выдать ошибку, а в catch — сообщение об ошибке:
try {
start(); // Uncaught ReferenceError: start is not defined
console.log("Привет"); // Этот код не выполнится
} catch (error) {
console.error("Ошибка поймана. Исключение:", error.message);
}
console.log("Ещё раз привет"); // Этот код выполнится
Исключение обработано. Теперь код выполнится дальше, но следующие строки кода в try не выполнятся, как и console.log("Привет");. Далее мы увидим действия из catch, а потом код продолжит выполняться.
Вывод в консоль будет таким:
❌index.html:23 Ошибка поймана. Исключение: start is not defined
Ещё раз привет
В JavaScript блок catch может быть только один, в отличие от Java, C++ или Python. Это связано с особенностью языка — он упрощён и адаптирован для разработки веб-сервисов.
Если нужно обработать несколько разных исключений за раз, используйте конструкцию switch или if…else для проверки типа внутри catch:
try {
// Код, который может вызвать исключение
} catch (error) {
switch (error.constructor) {
case TypeError:
// TypeError-исключение
console.error("Ошибка типа:", error.message);
break;
case ReferenceError:
// ReferenceError-исключение
console.error("Ошибка ссылки:", error.message);
break;
case SyntaxError:
// SyntaxError-исключение
console.error("Синтаксическая ошибка:", error.message);
break;
default:
console.error("Неизвестная ошибка:", error.message);
}
}
Блок finally в исключениях JavaScript
Блок finally — дополнительный блок в конструкции try…catch. Он выполняется в любом случае, было исключение или его не было, и пишется после блока catch. Такой блок необходим для завершения жизненно важных операций в случаях, когда произошла ошибка, всё сломалось и программа дальше работать не будет, но нужно сохранить данные или восстановить состояние.
В следующем JavaScript-коде всё хорошо, ошибок нет: catch не срабатывает, но событие в finally выполняется:
try {
// Тут всё хорошо
console.log("Начинаем выполнение");
let result = 2 + 2;
console.log("Результат:", result);
} catch (error) {
// Никаких ошибок не было — код в catch не выполнится
console.error("Ошибка:", error.message);
} finally {
// Выполнится в любом случае
console.log("Этот блок finally выполняется в любом случае");
}
Консоль JavaScript:
Начинаем выполнение
Результат: 4
Этот блок finally выполняется в любом случае
Есть ещё одна особенность: можно обойтись без catch и использовать только try и finally.
try {
JSON.parse("невалидный JSON"); // Ошибка!
} finally {
console.log("Финальный блок"); // Выполнится всегда
}
console.log("После try-finally"); // Не выполнится
В такой ситуации не будет проверок на ошибки, и блок finally гарантированно выполнится, работа программы хоть и некорректно, но завершится.
Консоль JavaScript:
Финальный блок
Объект Error и его свойства в JavaScript
Функция console.error("string", error) нужна, чтобы выводить в консоль форматированные ошибки. Она принимает два параметра, один из которых сама ошибка — объект класса Error.

Всё образовано от базового класса Object, унаследовано классом Error и распространяется на подклассы в зависимости от вида ошибки. За что отвечает каждый подкласс — описано выше.
У класса Error есть несколько основных свойств:
- name — имя класса ошибки;
- message — сообщение ошибки;
- stack — показать стек вызовов функций.
const err = new Error("Неверный тип"); // В конструкторе пишется message
console.log(err.name); // Имя класса ошибки
console.log(err.message); // Сообщение ошибки
console.log(err.stack); // Трассировка стека
- cause — ссылка на другую ошибку (другой Error), причина другой ошибки.
const original = new Error("Исходная ошибка");
const errWithCause = new Error("Новая ошибка", { cause: original });
console.log(errWithCause.cause); // Error: Исходная ошибка
console.log(errWithCause.message); // Error: Новая ошибка
Выброс исключения throw в JavaScript
По капотом исключения работают так:
- Где-то на стороне пользователя происходит ошибка.
- Вылетает исключение в блоке try JavaScript, текущий код прерывается.
- Программа ищет нужный класс ошибок Error.
- Передаёт найденную ошибку в catch.
- Выполняется finally (если есть).
- Программа продолжает работу после блока try…catch.
Если мы натыкаемся на какой-либо Error, где-то в JavaScript выполняется строчка:
// С помощью throw создаётся и выбрасывается ошибка
throw new TypeError("Сообщение об ошибке");
Можно создавать собственные ошибки. Исключение в JavaScript создаётся с помощью ключевого слова throw:
// Создаём свою ошибку и ловим её с помощью try...catch
try{
throw new Error("Новая ошибка");
}catch (error) {
console.log(error.message); // Вывод: Новая ошибка
}
Всё, что мы рассмотрели до этого, — синхронный код, то есть код, выполняющийся строчка за строчкой. Теперь рассмотрим особенности обработки исключений в случаях с асинхронным кодом.
Как правильно обрабатывать асинхронные ошибки в JavaScript
Асинхронный код в JavaScript — это код, который выполняется не сразу, а позже, после завершения других операций. Хотя JavaScript работает в одном потоке и выполняет команды по порядку, он умеет откладывать задачи, не мешая основному процессу. Это возможно благодаря очереди задач и механизму Event Loop.
Например, функция setTimeout() запускает другую функцию через заданное время. Но если внутри этой отложенной функции произойдёт ошибка, обычный try…catch её не поймает. Вот пример:
// Через 3 секунды произойдёт ошибка
// try...catch здесь работать не будет!
try{
setTimeout(() => {
throw new Error("Асинхронная ошибка");
}, 3000);
}catch(error) { console.error(error.message); }
console.log("Скоро будет конец..."); // Эта надпись появится за 3 секунды до исключения
В этом коде try…catch не сработает, потому что ошибка произойдёт позже, уже вне текущего блока. Сообщение «Скоро будет конец…» появится сразу, а ошибка — через 3 секунды, и она не будет обработана.
Чтобы правильно обрабатывать такие ошибки, нужно использовать другие подходы. Один из них — промисы (Promise). Это объект, который описывает результат асинхронной операции: если всё прошло успешно — вызывается resolve, если произошла ошибка — reject. Для обработки ошибок у промиса есть метод catch().
Вот как можно переписать пример с использованием промиса:
new Promise((_, reject) => {
setTimeout(() => {
reject(new Error("Асинхронная ошибка"));
}, 3000);
})
.catch(error => {
console.error("Ошибка поймана:", error.message);
});
console.log("Скоро будет конец...");
Теперь ошибка будет поймана и выведена в консоль. Такой подход работает и с сетевыми запросами, и с другими задачами, которые не выполняются мгновенно.
Для удобства можно использовать async/await — это синтаксис, который делает асинхронный код более читаемым, как будто он работает синхронно. Но даже в этом случае ошибки нужно оборачивать в try…catch внутри async-функции.
Модернизируем предыдущий пример и представим, что мы отправляем запрос на сервер, чтобы получить список товаров. Сервер может вернуть ошибку, поэтому сразу предусмотрим такой вариант:
// Объявляем асинхронную функцию для получения товаров
async function getProducts() {
try {
// Создаём промис, который «симулирует» работу сервера
// setTimeout через 3 секунды вызовет reject — то есть имитацию ошибки
await new Promise((resolve, reject) => {
setTimeout(() => {
// Здесь специально выбрасываем ошибку
reject(new Error("Сервер не отвечает"));
}, 3000);
});
// Если бы промис завершился успешно (resolve), код пошёл бы дальше
// Например:
// console.log("Данные получены!");
} catch (error) {
// Блок catch перехватывает ошибку и выводит сообщение о ней
console.log("Ошибка при получении данных:", error.message);
}
}
// Вызываем функцию
getProducts();
// Эта строка выполнится сразу, так как асинхронная функция не блокирует поток
console.log("Запрос отправлен, ждём ответа от сервера...");
- getProducts() — в нашем случае асинхронная функция, но async делает её похожей на синхронную.
- await — говорит, что нужно подождать, пока ответит сервер.
- reject(new Error()) — выбрасывает исключение, если «сервер не отвечает».
- try…catch — в данном примере поймает исключение, так как мы используем ключевые слова async/await. await можно и нужно оборачивать в try…catch, чтобы выловить исключение.
Практика: проектируем систему ошибок в приложении JavaScript
В большинстве приложений ошибки неизбежны. Проектирование системы ошибок помогает понять, что именно пошло не так, и ускоряет отладку. В этом уроке мы создадим собственную систему ошибок на JavaScript.
Шаг 1. Создаём HTML-файл для практики
Создайте файл index.html с базовой структурой:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Исключения JavaScript</title>
<script>
// Здесь будет ваш JavaScript-код
</script>
</head>
<body>
<h1>Практика: Проектирование системы ошибок в приложении JavaScript</h1>
</body>
</html>
В реальных проектах JavaScript обычно выносится в отдельный JS-файл, но для учебных целей мы пишем код прямо внутри тега <script>.
Шаг 2. Создаём базовый класс ошибок приложения
Добавим в блок <script> собственный класс AppError, который будет основой для всех ошибок в нашем приложении:
class AppError extends Error {
constructor(message, { code = "APP_ERROR", meta = {} } = {}) {
super(message);
this.name = this.constructor.name;
this.code = code;
this.meta = meta;
}
}
Этот класс позволяет задавать имя ошибки, код и дополнительные метаданные — удобно для логирования и отладки.
Шаг 3. Создаём специализированную ошибку — NetworkError
Теперь создадим класс NetworkError, который будет использоваться при отсутствии интернет-соединения:
class NetworkError extends AppError {
constructor(message = "Нет интернета", meta = {}) {
super(message, { code: "NETWORK_ERROR", meta });
}
}
Такая ошибка пригодится, если ваше приложение зависит от сетевых запросов.
Шаг 4. Пишем асинхронную функцию загрузки данных
Добавим функцию loadData(), которая имитирует сетевой запрос. Она использует ключевое слово async, а при отсутствии интернета выбрасывает исключение NetworkError.
async function loadData() {
const online = false; // Имитируем отсутствие интернета
if (!online) throw new NetworkError(); // Выбрасываем ошибку, если соединения нет
return ["object01", "object02"]; // Возвращаем данные, если соединение есть
}
Здесь видно, как проверка сетевого состояния встраивается в логику приложения.
Шаг 5. Вызываем функцию и обрабатываем исключения
Теперь вызовем loadData() внутри анонимной асинхронной функции. Мы используем конструкцию try…catch, чтобы перехватить возможные ошибки.
(async () => {
try {
const data = await loadData(); // Ожидаем результата
console.log(data); // Выводим данные в консоль
} catch (error) {
if (error instanceof NetworkError) {
console.warn("Проверьте подключение к интернету."); // Сообщение для пользователя
} else {
console.error("Ошибка:", error); // Лог других ошибок
}
}
})();
Шаг 6. Проверяем результат в консоли браузера
Откройте файл index.html в браузере и включите DevTools (обычно клавиша F12). Перейдите на вкладку Console.
Вы увидите предупреждение:
Проверьте подключение к интернету.
Это значит, что исключение NetworkError было успешно выброшено, перехвачено и обработано. Код работает корректно, и система ошибок функционирует как задумано.
Частые ошибки при работе с исключениями в JavaScript
Использовали await, но не защитили асинхронный код
// Объявляем асинхронную функцию fetchData
async function fetchData() {
// Делаем HTTP-запрос к API по указанному адресу и ждём ответа
const response = await fetch('https://example.com/api/data');
// Преобразуем ответ из формата JSON в объект JavaScript
const data = await response.json();
// Выводим полученные данные в консоль
console.log(data);
}
// Вызываем функцию, чтобы запустить загрузку данных
fetchData();
Если ссылка будет некорректной или сервер вернёт ошибку, то программа зависнет или прекратит свою работу. Всегда обёртывайте асинхронный код. В текущем примере с async и await нам будет достаточно использовать try…catch:
// Объявляем асинхронную функцию для получения данных
async function fetchData() {
try {
// Делаем HTTP-запрос по адресу 'https://example.com/api/data'
// fetch возвращает промис, поэтому используем await
const response = await fetch('https://example.com/api/data');
// Ответ от сервера нужно преобразовать в обычный объект/массив
// .json() тоже возвращает промис, поэтому снова ставим await
const data = await response.json();
// Выводим полученные данные в консоль
console.log(data);
} catch (error) {
// Если произошла ошибка (например, нет интернета или сервер не отвечает),
// то она попадёт сюда
console.error("Ошибка:", error.message);
}
}
// Запускаем функцию
fetchData();
Проигнорировали ошибки в промисах
// Функция fetchJSON принимает URL и возвращает промис
// 1) Вызываем fetch(url) — отправляем запрос на сервер
// 2) Когда ответ придёт, вызываем response.json()
// Это тоже возвращает промис, который преобразует данные в объект/массив
const fetchJSON = url => fetch(url).then(response => response.json());
// Вызываем функцию и передаём URL
fetchJSON('https://example.com/bad-data')
// Когда данные успешно получены и преобразованы в JSON,
// они попадают в .then(data => ...)
.then(data => console.log(data));
Функция fetch() — отвечает за отправку HTTP-запросов в JavaScript. Мы использовали функцию then() — она возвращает промис, но мы не указали функцию промиса catch() — в которой как раз будет обработана ошибка, если что-то пойдёт не так. Правильно написать код будет так:
// Вызываем функцию и передаём некорректный адрес
fetchJSON('https://example.com/bad-data')
// Если запрос успешный и данные корректные — попадём сюда
.then(data => console.log(data))
// Если произошла ошибка (например, сервер вернул плохие данные
// или JSON нельзя разобрать) — сработает .catch()
.catch(error => console.error("Ошибка:", error.message));
Повторили обработчик ошибок
Следующий код сильно нагружает систему постоянной проверкой try…catch каждого элемента массива и делает JavaScript-код непонятным:
// Создаём массив чисел от 1 до 10
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Перебираем массив методом forEach
numbers.forEach(num => {
try {
// Проверяем: если число нечётное — выбрасываем ошибку
if (num % 2 !== 0) throw new Error(`${num} нечётное`);
// Если число чётное — выводим его квадрат
console.log(num * num);
} catch (error) {
// Если поймали ошибку (нечётное число) — выводим сообщение
console.error("Ошибка:", error.message);
}
});
Исправим ситуацию, вытащив из цикла try…catch и обернув весь блок вне прохода по массиву. Технически теперь вместо десяти конструкций проверки исключений у нас остаётся только одна, производительность становится выше, а функциональность не меняется:
// Создаём массив чисел от 1 до 10
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Перебираем массив методом forEach
numbers.forEach(num => {
try {
// Проверяем: если число нечётное — выбрасываем ошибку
if (num % 2 !== 0) throw new Error(`${num} нечётное`);
// Если число чётное — выводим его квадрат
console.log(num * num);
} catch (error) {
// Если поймали ошибку (нечётное число) — выводим сообщение
console.error("Ошибка:", error.message);
}
});
Когда использовать исключения
Исключения требуют ресурсов для обработки и могут сильно тормозить программу. Обрабатывать весь код в try…catch не нужно, используйте его только в местах, где действительно может произойти критическая ошибка, но прерывать работу сервиса нельзя.
Есть несколько случаев, в которых оправданно использовать исключения.
Работа с сетью, файловой системой, базой данных
Обращения к внешним источникам данных всегда сопряжены с риском. Сервер может не ответить, соединение может оборваться или придёт некорректный ответ.
Если не предусмотреть такие ситуации, приложение просто остановится с ошибкой. Чтобы этого не случилось, такие запросы нужно оборачивать в try…catch. Это позволяет перехватить сбой, показать сообщение пользователю и продолжить работу без краха всей системы.
try {
// Отправляем HTTP-запрос на сервер
// await заставляет JS ждать ответа от сервера
const response = await fetch('https://api.example.com/data');
// Преобразуем ответ в формат JSON
// response.json() тоже возвращает промис, поэтому await нужен
const data = await response.json();
// Если всё прошло успешно, выводим данные в консоль
console.log(data);
} catch (error) {
// Если что-то пошло не так (сеть недоступна, сервер вернул ошибку,
// JSON не удалось разобрать), управление попадает сюда
console.error('Не удалось получить данные:', error.message);
// Можно вызвать функцию для показа ошибки пользователю
// Например, показать сообщение на странице
showErrorMessage('Сервис временно недоступен');
}
Парсинг входящих данных
Когда приложение получает данные извне — например, от сервера или из файла, — оно не может быть уверено, что формат будет правильным. Иногда приходит строка, которую нельзя разобрать как JSON: забыты кавычки, нарушена структура или просто передано не то, что ожидалось.
Если попробовать разобрать такую строку без проверки, программа может остановиться с ошибкой. Чтобы этого не произошло, нужно использовать try…catch — так можно перехватить исключение и продолжить работу, даже если данные оказались испорченными.
// Некорректный JSON
const userInput = '{ "Username": "Username123" }';
try {
// Пробуем разобрать JSON-строку в объект JavaScript
const obj = JSON.parse(userInput);
// Пытаемся обратиться к свойству name объекта
// Если в JSON такого поля нет, получим undefined
console.log(obj.name);
} catch (error) {
// Если JSON некорректный, выполнение перейдёт сюда
// Например, если забыты кавычки или синтаксис неверный
console.error('Ошибка парсинга JSON:', error.message);
}
Когда можно обойтись без исключений
Отсутствие ожидаемых данных
Если нужный элемент не найден в массиве, это не сбой, а обычная ситуация. Например, у нас есть пользователь с несуществующим ID. Вместо исключения можно спокойно проверить это через if и вывести понятное сообщение. Ошибкой это не считается, и выбрасывать её не нужно.
// Массив объектов пользователей
const users = [
{ id: 1, name: 'Ann' },
{ id: 2, name: 'Kate' }
];
// Пытаемся найти пользователя с id = 3
// Метод .find() возвращает первый элемент, который подходит под условие
// Если такого элемента нет, вернёт undefined
const user = users.find(u => u.id === 3);
// Проверяем, найден ли пользователь
if (!user) {
// Если user === undefined (не найден) — выводим сообщение
console.log('Пользователь не найден');
} else {
// Если пользователь найден — выводим его имя
console.log('Имя пользователя:', user.name);
}
Валидация данных от пользователя
Проверка данных, которые вводит пользователь, — это обычная часть работы приложения. Если, например, человек ввёл email без символа @, это не сбой программы, а просто некорректный ввод.
В таких случаях не нужно выбрасывать исключения. Гораздо проще и правильнее проверить данные через if, показать сообщение и попросить ввести заново. Это быстрее, понятнее и не перегружает систему.
// Функция проверяет, содержит ли строка символ @
// Простая проверка для валидности email
function isEmail(value) {
return value.includes('@'); // true, если есть @, иначе false
}
// Пример введённого пользователем email
const email = 'testmail.com';
// Проверяем email с помощью функции isEmail
if (!isEmail(email)) {
// Если функция вернула false (нет @) — показываем сообщение об ошибке
showErrorMessage('Введите корректный email');
} else {
// Если функция вернула true, считаем email корректным
console.log('Email принят');
}
Что ещё почитать
1. MDN Web Docs: управление потоком и обработка ошибок
Объясняет, как работают try…catch, throw, finally и в каких случаях их использовать. Есть примеры синхронного и асинхронного кода, а также рекомендации по стилю.
2. MDN Web Docs: объект Error и его типы
Подробно описывает встроенные типы ошибок: Error, TypeError, SyntaxError, ReferenceError и другие. Рассказывает, как создавать свои классы ошибок и какие свойства доступны.
3. MDN Web Docs: Promise.prototype.catch()
Показывает, как правильно обрабатывать ошибки в промисах. Есть примеры использования .catch() и объяснение, почему его нельзя пропускать.
4. ECMAScript Language Specification (ECMA-262)
Официальная спецификация языка JavaScript. В ней описано поведение исключений на уровне стандарта: как работает throw, как интерпретатор обрабатывает ошибки и какие объекты участвуют в процессе.
Больше интересного про код — в нашем телеграм-канале. Подписывайтесь!