В любом коде возможны ошибки. В случае, когда такая ошибка встречается в JavaScript, интерпретатор прекращает выполнение кода и выводит в консоль браузера сообщение об ошибке. Для того чтобы ваш код на JS оставался работоспособным, эти ошибки имеет смысл перехватывать.
Для перехвата ошибки необходимо возбудить, или "выбросить" исключение. В терминах программирования за это отвечает инструкция throw - она нужна для описания того, что будет делать JavaScript, когда он встречает что-то, что не может быть обработано. Используя инструкцию throw
, вы технически генерируете исключение. Фактически после ключевого слова throw
мы должны вывести какое-то сообщение об ошибке, которое дальше нужно будет как-то обработать, например, вывести в console.error()
.
В документации на MDN об инструкции throw
написано следующее:
Инструкция
throw
позволяет генерировать исключения, определяемые пользователем. При этом выполнение текущей функции будет остановлено (инструкции послеthrow
не будут выполнены), и управление будет передано в первый блокcatch
в стеке вызовов. Еслиcatch
блоков среди вызванных функций нет, выполнение программы будет остановлено.
Генерация исключения с помощью throw
Давайте попробуем использовать генерацию исключения в примере с назначением через JavaScript стилевых свойств для абзаца с id="testBlock"
. Поскольку все доступные для html-элементов стилевые свойства известны браузеру, мы будем их проверять с помощью оператора in
, который возвращает true
, если свойство содержится в указанном объекте. В том случае, если мы вызываем в функции setStyle()
существующее свойство, оно применится к абзацу. Если нет, то будет выброшено исключение, которое с помощью инструкции throw
выведет ошибку в консоль браузера.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function setStyle(str, value) { if (str in testBlock.style) { testBlock.style[str] = value; } else { throw new Error("Переданная строка не является CSS-свойством"); } } //console.log(testBlock.style); // вы можете посмотреть все доступные стилевые свойства для html-элемента setStyle("color", 'green'); setStyle("background-color", 'lime'); setStyle("margin", '20px'); setStyle("font-size", '24px'); setStyle("next-style", '40vh');// несуществующее свойство - ошибка setStyle("text-transform", 'uppercase'); //не выводится |
Назначьте стили для абзаца, нажав на кнопку:
Тестируем стилевые свойства параграфа
Если вы проанализируете, какие стили были назначены, то увидите, что после ошибочного стиля, который мы пытались назначить строкой setStyle("next-style", '40vh')
, последний существующий стиль из строки setStyle("text-transform", 'uppercase')
уже не применился, т.к. была выдана ошибка, сгенерированная инструкцией throw
, и дальнейшее выполнение кода было остановлено.
В консоли вы увидите текст Uncaught Error: Переданная строка не является CSS-свойством.
В нашем случае Uncaught Error - это неперехваченная ошибка, которая не относится ни к одному из стандартных типов ошибок объекта Error в JavaScript. Обычно интерпретатор JavaScript возбуждает исключения с именами 'SyntaxError', 'TypeError', 'RangeError', 'ReferenceError', 'URIError' или 'InternalError'.
Перехват ошибок в конструкции try ... catch ... finally
Для того чтобы перехватить возникающие ошибки в коде, в JavaScript существует конструкция try ... catch ... finally
, синтаксис которой будет таким:
1 2 3 4 5 6 7 8 9 10 | try { оператор 1; оператор 2; ... оператор n; } catch { обработчик ошибки/исключения; } finally { финальный код; } |
Чаще всего в блоке try
используется инструкция throw
в случае, если код может вызвать ошибку, а в блоке catch
эта ошибка как-то выводится или обрабатывается.
Варианты использования конструкции try ... catch ... finally
Конструкция try ... catch ... finally
всегда начинается с блока try
, в котором находится одна или несколько инструкций (операторов). В ряде случаев там может быть всего одна строка, например, вызов какой-либо функции. А вот после блока try
может идти блок catch
или блок finally
. То есть вы можете использовать один из вариантов конструкции try ... catch ... finally
:
try {...} catch {...}
try {...} finally {...}
try {...} catch {...} finally {...}
Если перевести с английского инструкции, заданные ключевыми словами try ... catch ... finally
, то в первом блоке мы будем пытаться (try
) выполнить некий код, в блоке catch
мы будем "ловить" ошибки, а в блок finally
сработает в любом случае в самом конце, но только после того, как будет выполнен код в одном из предыдущих блоков.
В случае успешного выполнения кода в блоке try
на этом все может закончиться, если отсутствует блок finally
. Т. е. блок catch
не выполняется, поскольку ошибки нет.
Инструкции (операторы) в блоке catch
будут выполнены, если в блоке try
произошла ошибка. Когда любой код в блоке try
выбрасывает исключение, то управление сразу же переходит в блок catch
.
Блок finally
выполнится после выполнения блоков try
и catch
, но перед любым кодом, который идет после конструкции try...catch
. Он выполняется всегда, независимо от того, было исключение (ошибка) или нет.
Например, мы создаем простую функцию суммы, в которой складываем 2 числа и возвращаем результат сложения. Однако, если в такую функцию будет передано не число, а строка, то произойдет конкатенация строк и результат сложения будет некорректным. Давайте попробуем сообщить об этом пользователю, используя конструкцию try ... catch
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function summa(a, b){ try { if (typeof a !== "number") { throw `Ошибка: ${a} не является числом`; } if (typeof b !== "number") { throw `Ошибка: ${b} не является числом`; } return a+b; } catch (err) { console.error(err); return "Введены некорректные значения"; } } alert(summa(10, "25")); //"Введены некорректные значения" alert(summa(2,45)) // 47 |
В результате первого вызова функции мы получим в диалоговом окне alert() сообщение "Введены некорректные значения", а во втором случае число 47.
Если добавить в эту функцию блок finally
и несколько переписать сам код, то мы получим совсем другой результат. Здесь мы будем использовать для проверки на числовой тип не оператор typeof
, а функцию isNaN()
, а также метод parseFloat()
для преобразования строки в число. Этот метод умеет отсекать строки от чисел, если строка следует за числом, но не наоборот.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function summa1(a, b){ try { if ( isNaN( parseFloat(a) )) { throw `Ошибка: ${a} не является числом`; } if ( isNaN( parseFloat(b) ) ) { throw `Ошибка: ${b} не является числом`; } } catch (err) { console.error(err); return "Введены некорректные значения"; } finally { return parseFloat(a) + parseFloat(b); } } alert(summa1(10, 145)); //155 alert(summa1(10, "25")); //35 alert(summa1(10,"82.5abc")); // 92.5 alert(summa1(10,"abc39")); //NaN |
Обратите внимание, что строка, подсчитывающая сумму с учетом преобразования строки в число методом parseFloat()
"переехала" в блок finally
, поэтому сообщение об ошибке, которое выводится в console.error() мы видим, но в alert()
получаем NaN
из блока finally
, а не сообщение "Введены некорректные значения" из блока catch
.
Практическое применение try ... catch в функции, определяющей расширение файла
Давайте рассмотрим чуть более сложную задачу, в которой нам нужно определить расширение файла в строке, передаваемой в функцию. Внутри этой функции мы попытаемся найти в строке методом lastIndexOf('.')
символ точки. Если он найден, то мы выведем это расширение, если символ точки отсутствует или есть только в конце строки, то мы выводим информацию о том, что у файла нет расширения. В том же случае, когда функция вызывается с параметром в виде числа, сработает код в блоке catch
, и мы получим сообщение об ошибке и вывод текста "неверный формат".
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function fileExtension(str) { let ext = ''; try { let pos = str.lastIndexOf('.'); if (pos > -1 && pos!=str.length-1) ext = " расширение " + str.slice(pos + 1); else ext = " нет раширения"; return ext; } catch (err) { alert(err.name + ": " + err.message); return " неверный формат"; } } document.write('У файла <strong>document.docx</strong> ' + fileExtension('document.docx') + '<br>'); document.write('У файла <strong>page-about.html</strong> ' + fileExtension('page-about.html') + '<br>'); document.write('У файла <strong>Это просто предложение, без названия файла, но с точкой в конце.</strong> ' + fileExtension('Это просто предложение, без названия файла, но с точкой в конце.') + '<br>'); document.write('У файла <strong>document_docx</strong> ' + fileExtension('document_docx') + '<br>'); document.write('У файла <strong>123</strong> ' + fileExtension(123) + '<br>'); |
Попробуйте сами:
Используем try...catch для парсинга JSON-формата
Как правило, мы получаем из JSON-файла строку и преобразуем ее в объект для дальнейшего использования. В том случае, когда вызов метода JSON.parse()
приводит к ошибке, мы генерируем исключение и обрабатываем эту ситуацию в ветви catch
:
See the Pen Untitled by Elen (@ambassador) on CodePen.
В примере выше мы не загружаем JSON-файл, а проверяем обычный объект и объект, преобразованный в формат JSON методом JSON.stringify()
, а затем выводим данные об ошибке (о том, что получена некорректная JSON-строка) или выводим содержимое JSON.parse()
.
try..catch
не поймает ошибку, которая произойдёт в коде, записанном в виде функции в таких методах, как setTimeout()
или setInterval()
с отложенным временем выполнения в виде задержки в миллисекундах.Например:
1 2 3 4 5 6 7 8 9 | let num = +prompt('Введите число от 1 до 10'); try { setTimeout(function() { if(num<1 || num>10 || isNaN(num)) throw new Error('Ошибка в консоли! Неверный ввод!'); else alert("Вы ввели число "+num); }, 500); } catch (e) { alert( "Ошибка в блоке catch" ); } |
Попробуйте сами:
Если вы введете число больше 10, то не увидите никакой ошибки в виде диалогового окна alert()
, зато в консоли обнаружите ошибку в виде
Использование конструкции try...catch...finally при асинхронной загрузке данных в функциях с async/await
На данный момент для загрузки данных становится все более популярным использование async/await функций. Однако и в них могут происходить ошибки. Поэтому вы можете использовать блоки try ... catch
для обработки ошибок в асинхронных функциях.
Например, у нас есть необходимость получать асинхронно какую-либо информацию из JSON-файла. Мы будем использовать возможность загрузки фейкового контента с сервиса JSONPlaceholder. В функции fetchTodos()
будем загружать "список дел" для виртуальных пользователей.
1 2 3 4 5 6 7 8 9 10 11 12 | async function fetchTodos() { try{ const response = await fetch("https://jsonplaceholder.typicode.com/todos/"); const todos = await response.json(); } catch (err){ //обработка ошибки console.log(err); } finally { //вывод информации } } fetchTodos(); |
Измените адрес для загрузки контента в 4-й строке - и увидите ошибку в div с id="wrap"
.
See the Pen Random Quote Machine by Elen (@ambassador) on CodePen.