Ошибки — это хорошо. Автор материала, перевод которого мы сегодня публикуем, говорит, что уверен в том, что эта идея известна всем. На первый взгляд ошибки кажутся чем-то страшным. Им могут сопутствовать какие-то потери. Ошибка, сделанная на публике, вредит авторитету того, кто её совершил. Но, совершая ошибки, мы на них учимся, а значит, попадая в следующий раз в ситуацию, в которой раньше вели себя неправильно, делаем всё как нужно.
Выше мы говорили об ошибках, которые люди совершают в обычной жизни. Ошибки в программировании — это нечто иное. Сообщения об ошибках помогают нам улучшать код, они позволяют сообщать пользователям наших проектов о том, что что-то пошло не так, и, возможно, рассказывают пользователям о том, как нужно вести себя для того, чтобы ошибок больше не возникало.
Этот материал, посвящённый обработке ошибок в JavaScript, разбит на три части. Сначала мы сделаем общий обзор системы обработки ошибок в JavaScript и поговорим об объектах ошибок. После этого мы поищем ответ на вопрос о том, что делать с ошибками, возникающими в серверном коде (в частности, при использовании связки Node.js + Express.js). Далее — обсудим обработку ошибок в React.js. Фреймворки, которые будут здесь рассматриваться, выбраны по причине их огромной популярности. Однако рассматриваемые здесь принципы работы с ошибками универсальны, поэтому вы, даже если не пользуетесь Express и React, без труда сможете применить то, что узнали, к тем инструментам, с которыми работаете.
Код демонстрационного проекта, используемого в данном материале, можно найти в этом репозитории.
1. Ошибки в JavaScript и универсальные способы работы с ними
Если в вашем коде что-то пошло не так, вы можете воспользоваться следующей конструкцией.
throw new Error('something went wrong')
В ходе выполнения этой команды будет создан экземпляр объекта Error и будет сгенерировано (или, как говорят, «выброшено») исключение с этим объектом. Инструкция throw может генерировать исключения, содержащие произвольные выражения. При этом выполнение скрипта остановится в том случае, если не были предприняты меры по обработке ошибки.
Начинающие JS-программисты обычно не используют инструкцию throw. Они, как правило, сталкиваются с исключениями, выдаваемыми либо средой выполнения языка, либо сторонними библиотеками. Когда это происходит — в консоль попадает нечто вроде ReferenceError: fs is not defined и выполнение программы останавливается.
▍Объект Error
У экземпляров объекта Error есть несколько свойств, которыми мы можем пользоваться. Первое интересующее нас свойство — message. Именно сюда попадает та строка, которую можно передать конструктору ошибки в качестве аргумента. Например, ниже показано создание экземпляра объекта Error и вывод в консоль переданной конструктором строки через обращение к его свойству message.
const myError = new Error('please improve your code')
console.log(myError.message) // please improve your code
Второе свойство объекта, очень важное, представляет собой трассировку стека ошибки. Это — свойство stack. Обратившись к нему можно просмотреть стек вызовов (историю ошибки), который показывает последовательность операций, приведшую к неправильной работе программы. В частности, это позволяет понять — в каком именно файле содержится сбойный код, и увидеть, какая последовательность вызовов функций привела к ошибке. Вот пример того, что можно увидеть, обратившись к свойству stack.
Error: please improve your code
at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
at startup (internal/bootstrap/node.js:266:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)
Здесь, в верхней части, находится сообщение об ошибке, затем следует указание на тот участок кода, выполнение которого вызвало ошибку, потом описывается то место, откуда был вызван этот сбойный участок. Это продолжается до самого «дальнего» по отношению к ошибке фрагмента кода.
▍Генерирование и обработка ошибок
Создание экземпляра объекта Error, то есть, выполнение команды вида new Error(), ни к каким особым последствиям не приводит. Интересные вещи начинают происходить после применения оператора throw, который генерирует ошибку. Как уже было сказано, если такую ошибку не обработать, выполнение скрипта остановится. При этом нет никакой разницы — был ли оператор throw использован самим программистом, произошла ли ошибка в некоей библиотеке или в среде выполнения языка (в браузере или в Node.js). Поговорим о различных сценариях обработки ошибок.
▍Конструкция try…catch
Блок try...catch представляет собой самый простой способ обработки ошибок, о котором часто забывают. В наши дни, правда, он используется гораздо интенсивнее чем раньше, благодаря тому, что его можно применять для обработки ошибок в конструкциях async/await.
Этот блок можно использовать для обработки любых ошибок, происходящих в синхронном коде. Рассмотрим пример.
const a = 5
try {
console.log(b) // переменная b не объявлена - возникает ошибка
} catch (err) {
console.error(err) // в консоль попадает сообщение об ошибке и стек ошибки
}
console.log(a) // выполнение скрипта не останавливается, данная команда выполняется
Если бы в этом примере мы не заключили бы сбойную команду console.log(b) в блок try...catch, то выполнение скрипта было бы остановлено.
▍Блок finally
Иногда случается так, что некий код нужно выполнить независимо от того, произошла ошибка или нет. Для этого можно, в конструкции try...catch, использовать третий, необязательный, блок — finally. Часто его использование эквивалентно некоему коду, который идёт сразу после try...catch, но в некоторых ситуациях он может пригодиться. Вот пример его использования.
const a = 5
try {
console.log(b) // переменная b не объявлена - возникает ошибка
} catch (err) {
console.error(err) // в консоль попадает сообщение об ошибке и стек ошибки
} finally {
console.log(a) // этот код будет выполнен в любом случае
}
▍Асинхронные механизмы — коллбэки
Программируя на JavaScript всегда стоит обращать внимание на участки кода, выполняющиеся асинхронно. Если у вас имеется асинхронная функция и в ней возникает ошибка, скрипт продолжит выполняться. Когда асинхронные механизмы в JS реализуются с использованием коллбэков (кстати, делать так не рекомендуется), соответствующий коллбэк (функция обратного вызова) обычно получает два параметра. Это нечто вроде параметра err, который может содержать ошибку, и result — с результатами выполнения асинхронной операции. Выглядит это примерно так:
myAsyncFunc(someInput, (err, result) => {
if(err) return console.error(err) // порядок работы с объектом ошибки мы рассмотрим позже
console.log(result)
})
Если в коллбэк попадает ошибка, она видна там в виде параметра err. В противном случае в этот параметр попадёт значение undefined или null. Если оказалось, что в err что-то есть, важно отреагировать на это, либо так как в нашем примере, воспользовавшись командой return, либо воспользовавшись конструкцией if...else и поместив в блок else команды для работы с результатом выполнения асинхронной операции. Речь идёт о том, чтобы, в том случае, если произошла ошибка, исключить возможность работы с результатом, параметром result, который в таком случае может иметь значение undefined. Работа с таким значением, если предполагается, например, что оно содержит объект, сама может вызвать ошибку. Скажем, это произойдёт при попытке использовать конструкцию result.data или подобную ей.
▍Асинхронные механизмы — промисы
Для выполнения асинхронных операций в JavaScript лучше использовать не коллбэки а промисы. Тут, в дополнение к улучшенной читабельности кода, имеются и более совершенные механизмы обработки ошибок. А именно, возиться с объектом ошибки, который может попасть в функцию обратного вызова, при использовании промисов не нужно. Здесь для этой цели предусмотрен специальный блок catch. Он перехватывает все ошибки, произошедшие в промисах, которые находятся до него, или все ошибки, которые произошли в коде после предыдущего блока catch. Обратите внимание на то, что если в промисе произошла ошибка, для обработки которой нет блока catch, это не остановит выполнение скрипта, но сообщение об ошибке будет не особенно удобочитаемым.
(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong
(node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */
В результате можно порекомендовать всегда, при работе с промисами, использовать блок catch. Взглянем на пример.
Promise.resolve(1)
.then(res => {
console.log(res) // 1
throw new Error('something went wrong')
return Promise.resolve(2)
})
.then(res => {
console.log(res) // этот блок выполнен не будет
})
.catch(err => {
console.error(err) // о том, что делать с этой ошибкой, поговорим позже
return Promise.resolve(3)
})
.then(res => {
console.log(res) // 3
})
.catch(err => {
// этот блок тут на тот случай, если в предыдущем блоке возникнет какая-нибудь ошибка
console.error(err)
})
▍Асинхронные механизмы и try…catch
После того, как в JavaScript появилась конструкция async/await, мы вернулись к классическому способу обработки ошибок — к try...catch...finally. Обрабатывать ошибки при таком подходе оказывается очень легко и удобно. Рассмотрим пример.
;(async function() {
try {
await someFuncThatThrowsAnError()
} catch (err) {
console.error(err) // об этом поговорим позже
}
console.log('Easy!') // будет выполнено
})()
При таком подходе ошибки в асинхронном коде обрабатываются так же, как в синхронном. В результате теперь, при необходимости, в одном блоке catch можно обрабатывать более широкий диапазон ошибок.
2. Генерирование и обработка ошибок в серверном коде
Теперь, когда у нас есть инструменты для работы с ошибками, посмотрим на то, что мы можем с ними делать в реальных ситуациях. Генерирование и правильная обработка ошибок — это важнейший аспект серверного программирования. Существуют разные подходы к работе с ошибками. Здесь будет продемонстрирован подход с использованием собственного конструктора для экземпляров объекта Error и кодов ошибок, которые удобно передавать во фронтенд или любым механизмам, использующим серверные API. Как структурирован бэкенд конкретного проекта — особого значения не имеет, так как при любом подходе можно использовать одни и те же идеи, касающиеся работы с ошибками.
В качестве серверного фреймворка, отвечающего за маршрутизацию, мы будем использовать Express.js. Подумаем о том, какая структура нам нужна для организации эффективной системы обработки ошибок. Итак, вот что нам нужно:
- Универсальная обработка ошибок — некий базовый механизм, подходящий для обработки любых ошибок, в ходе работы которого просто выдаётся сообщение наподобие
Something went wrong, please try again or contact us, предлагающее пользователю попробовать выполнить операцию, давшую сбой, ещё раз или связаться с владельцем сервера. Эта система не отличается особой интеллектуальностью, но она, по крайней мере, способна сообщить пользователю о том, что что-то пошло не так. Подобное сообщение гораздо лучше, чем «бесконечная загрузка» или нечто подобное. - Обработка конкретных ошибок — механизм, позволяющий сообщить пользователю подробные сведения о причинах неправильного поведения системы и дать ему конкретные советы по борьбе с неполадкой. Например, это может касаться отсутствия неких важных данных в запросе, который пользователь отправляет на сервер, или в том, что в базе данных уже существует некая запись, которую он пытается добавить ещё раз, и так далее.
▍Разработка собственного конструктора объектов ошибок
Здесь мы воспользуемся стандартным классом Error и расширим его. Пользоваться механизмами наследования в JavaScript — дело рискованное, но в данном случае эти механизмы оказываются весьма полезными. Зачем нам наследование? Дело в том, что нам, для того, чтобы код удобно было бы отлаживать, нужны сведения о трассировке стека ошибки. Расширяя стандартный класс Error, мы, без дополнительных усилий, получаем возможности по трассировке стека. Мы добавляем в наш собственный объект ошибки два свойства. Первое — это свойство code, доступ к которому можно будет получить с помощью конструкции вида err.code. Второе — свойство status. В него будет записываться код состояния HTTP, который планируется передавать клиентской части приложения.
Вот как выглядит класс CustomError, код которого оформлен в виде модуля.
class CustomError extends Error {
constructor(code = 'GENERIC', status = 500, ...params) {
super(...params)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomError)
}
this.code = code
this.status = status
}
}
module.exports = CustomError
▍Маршрутизация
Теперь, когда наш объект ошибки готов к использованию, нужно настроить структуру маршрутов. Как было сказано выше, нам требуется реализовать унифицированный подход к обработке ошибок, позволяющий одинаково обрабатывать ошибки для всех маршрутов. По умолчанию фреймворк Express.js не вполне поддерживает такую схему работы. Дело в том, что все его маршруты инкапсулированы.
Для того чтобы справиться с этой проблемой, мы можем реализовать собственный обработчик маршрутов и определять логику маршрутов в виде обычных функций. Благодаря такому подходу, если функция маршрута (или любая другая функция) выбрасывает ошибку, она попадёт в обработчик маршрутов, который затем может передать её клиентской части приложения. При возникновении ошибки на сервере мы планируем передавать её во фронтенд в следующем формате, полагая, что для этого будет применяться JSON-API:
{
error: 'SOME_ERROR_CODE',
description: 'Something bad happened. Please try again or contact support.'
}
Если на данном этапе происходящие кажется вам непонятным — не беспокойтесь — просто продолжайте читать, пробуйте работать с тем, о чём идёт речь, и постепенно вы во всём разберётесь. На самом деле, если говорить о компьютерном обучении, здесь применяется подход «сверху-вниз», когда сначала обсуждаются общие идеи, а потом осуществляется переход к частностям.
Вот как выглядит код обработчика маршрутов.
const express = require('express')
const router = express.Router()
const CustomError = require('../CustomError')
router.use(async (req, res) => {
try {
const route = require(`.${req.path}`)[req.method]
try {
const result = route(req) // Передаём запрос функции route
res.send(result) // Передаём клиенту то, что получено от функции route
} catch (err) {
/*
Сюда мы попадаем в том случае, если в функции route произойдёт ошибка
*/
if (err instanceof CustomError) {
/*
Если ошибка уже обработана - трансформируем её в
возвращаемый объект
*/
return res.status(err.status).send({
error: err.code,
description: err.message,
})
} else {
console.error(err) // Для отладочных целей
// Общая ошибка - вернём универсальный объект ошибки
return res.status(500).send({
error: 'GENERIC',
description: 'Something went wrong. Please try again or contact support.',
})
}
}
} catch (err) {
/*
Сюда мы попадём, если запрос окажется неудачным, то есть,
либо не будет найдено файла, соответствующего пути, переданному
в запросе, либо не будет экспортированной функции с заданным
методом запроса
*/
res.status(404).send({
error: 'NOT_FOUND',
description: 'The resource you tried to access does not exist.',
})
}
})
module.exports = router
Полагаем, комментарии в коде достаточно хорошо его поясняют. Надеемся, читать их удобнее, чем объяснения подобного кода, данные после него.
Теперь взглянем на файл маршрутов.
const CustomError = require('../CustomError')
const GET = req => {
// пример успешного выполнения запроса
return { name: 'Rio de Janeiro' }
}
const POST = req => {
// пример ошибки общего характера
throw new Error('Some unexpected error, may also be thrown by a library or the runtime.')
}
const DELETE = req => {
// пример ошибки, обрабатываемой особым образом
throw new CustomError('CITY_NOT_FOUND', 404, 'The city you are trying to delete could not be found.')
}
const PATCH = req => {
// пример перехвата ошибок и использования CustomError
try {
// тут случилось что-то нехорошее
throw new Error('Some internal error')
} catch (err) {
console.error(err) // принимаем решение о том, что нам тут делать
throw new CustomError(
'CITY_NOT_EDITABLE',
400,
'The city you are trying to edit is not editable.'
)
}
}
module.exports = {
GET,
POST,
DELETE,
PATCH,
}
В этих примерах с самими запросами ничего не делается. Тут просто рассматриваются разные сценарии возникновения ошибок. Итак, например, запрос GET /city попадёт в функцию const GET = req =>..., запрос POST /city попадёт в функцию const POST = req =>... и так далее. Эта схема работает и при использовании параметров запросов. Например — для запроса вида GET /city?startsWith=R. В целом, здесь продемонстрировано, что при обработке ошибок, во фронтенд может попасть либо общая ошибка, содержащая лишь предложение попробовать снова или связаться с владельцем сервера, либо ошибка, сформированная с использованием конструктора CustomError, которая содержит подробные сведения о проблеме.
Данные общей ошибки придут в клиентскую часть приложения в таком виде:
{
error: 'GENERIC',
description: 'Something went wrong. Please try again or contact support.'
}
Конструктор CustomError используется так:
throw new CustomError('MY_CODE', 400, 'Error description')
Это даёт следующий JSON-код, передаваемый во фронтенд:
{
error: 'MY_CODE',
description: 'Error description'
}
Теперь, когда мы основательно потрудились над серверной частью приложения, в клиентскую часть больше не попадают бесполезные логи ошибок. Вместо этого клиент получает полезные сведения о том, что пошло не так.
Не забудьте о том, что здесь лежит репозиторий с рассматриваемым здесь кодом. Можете его загрузить, поэкспериментировать с ним, и, если надо, адаптировать под нужды вашего проекта.
3. Работа с ошибками на клиенте
Теперь пришла пора описать третью часть нашей системы обработки ошибок, касающуюся фронтенда. Тут нужно будет, во-первых, обрабатывать ошибки, возникающие в клиентской части приложения, а во-вторых, понадобится оповещать пользователя об ошибках, возникающих на сервере. Разберёмся сначала с показом сведений о серверных ошибках. Как уже было сказано, в этом примере будет использована библиотека React.
▍Сохранение сведений об ошибках в состоянии приложения
Как и любые другие данные, ошибки и сообщения об ошибках могут меняться, поэтому их имеет смысл помещать в состояние компонентов. При монтировании компонента данные об ошибке сбрасываются, поэтому, когда пользователь впервые видит страницу, там сообщений об ошибках не будет.
Следующее, с чем надо разобраться, заключается в том, что ошибки одного типа нужно показывать в одном стиле. По аналогии с сервером, здесь можно выделить 3 типа ошибок.
- Глобальные ошибки — в эту категорию попадают сообщения об ошибках общего характера, приходящие с сервера, или ошибки, которые, например, возникают в том случае, если пользователь не вошёл в систему и в других подобных ситуациях.
- Специфические ошибки, выдаваемые серверной частью приложения — сюда относятся ошибки, сведения о которых приходят с сервера. Например, подобная ошибка возникает, если пользователь попытался войти в систему и отправил на сервер имя и пароль, а сервер сообщил ему о том, что пароль неправильный. Подобные вещи в клиентской части приложения не проверяются, поэтому сообщения о таких ошибках должны приходить с сервера.
- Специфические ошибки, выдаваемые клиентской частью приложения. Пример такой ошибки — сообщение о некорректном адресе электронной почты, введённом в соответствующее поле.
Ошибки второго и третьего типов очень похожи, работать с ними можно, используя хранилище состояния компонентов одного уровня. Их главное различие заключается в том, что они исходят из разных источников. Ниже, анализируя код, мы посмотрим на работу с ними.
Здесь будет использоваться встроенная в React система управления состоянием приложения, но, при необходимости, вы можете воспользоваться и специализированными решениями для управления состоянием — такими, как MobX или Redux.
▍Глобальные ошибки
Обычно сообщения о таких ошибках сохраняются в компоненте наиболее высокого уровня, имеющем состояние. Они выводятся в статическом элементе пользовательского интерфейса. Это может быть красное поле в верхней части экрана, модальное окно или что угодно другое. Реализация зависит от конкретного проекта. Вот как выглядит сообщение о такой ошибке.
Сообщение о глобальной ошибке
Теперь взглянем на код, который хранится в файле Application.js.
import React, { Component } from 'react'
import GlobalError from './GlobalError'
class Application extends Component {
constructor(props) {
super(props)
this.state = {
error: '',
}
this._resetError = this._resetError.bind(this)
this._setError = this._setError.bind(this)
}
render() {
return (
<div className="container">
<GlobalError error={this.state.error} resetError={this._resetError} />
<h1>Handling Errors</h1>
</div>
)
}
_resetError() {
this.setState({ error: '' })
}
_setError(newError) {
this.setState({ error: newError })
}
}
export default Application
Как видно, в состоянии, в Application.js, имеется место для хранения данных ошибки. Кроме того, тут предусмотрены методы для сброса этих данных и для их изменения.
Ошибка и метод для сброса ошибки передаётся компоненту GlobalError, который отвечает за вывод сообщения об ошибке на экран и за сброс ошибки после нажатия на значок x в поле, где выводится сообщение. Вот код компонента GlobalError (файл GlobalError.js).
import React, { Component } from 'react'
class GlobalError extends Component {
render() {
if (!this.props.error) return null
return (
<div
style={{
position: 'fixed',
top: 0,
left: '50%',
transform: 'translateX(-50%)',
padding: 10,
backgroundColor: '#ffcccc',
boxShadow: '0 3px 25px -10px rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
}}
>
{this.props.error}
<i
className="material-icons"
style={{ cursor: 'pointer' }}
onClick={this.props.resetError}
>
close
</font></i>
</div>
)
}
}
export default GlobalError
Обратите внимание на строку if (!this.props.error) return null. Она указывает на то, что при отсутствии ошибки компонент ничего не выводит. Это предотвращает постоянный показ красного прямоугольника на странице. Конечно, вы, при желании, можете поменять внешний вид и поведение этого компонента. Например, вместо того, чтобы сбрасывать ошибку по нажатию на x, можно задать тайм-аут в пару секунд, по истечении которого состояние ошибки сбрасывается автоматически.
Теперь, когда всё готово для работы с глобальными ошибками, для задания глобальной ошибки достаточно воспользоваться _setError из Application.js. Например, это можно сделать в том случае, если сервер, после обращения к нему, вернул сообщение об общей ошибке (error: 'GENERIC'). Рассмотрим пример (файл GenericErrorReq.js).
import React, { Component } from 'react'
import axios from 'axios'
class GenericErrorReq extends Component {
constructor(props) {
super(props)
this._callBackend = this._callBackend.bind(this)
}
render() {
return (
<div>
<button onClick={this._callBackend}>Click me to call the backend</button>
</div>
)
}
_callBackend() {
axios
.post('/api/city')
.then(result => {
// сделать что-нибудь с результатом в том случае, если запрос оказался успешным
})
.catch(err => {
if (err.response.data.error === 'GENERIC') {
this.props.setError(err.response.data.description)
}
})
}
}
export default GenericErrorReq
На самом деле, на этом наш разговор об обработке ошибок можно было бы и закончить. Даже если в проекте нужно оповещать пользователя о специфических ошибках, никто не мешает просто поменять глобальное состояние, хранящее ошибку и вывести соответствующее сообщение поверх страницы. Однако тут мы не остановимся и поговорим о специфических ошибках. Во-первых, это руководство по обработке ошибок иначе было бы неполным, а во-вторых, с точки зрения UX-специалистов, неправильно будет показывать сообщения обо всех ошибках так, будто все они — глобальные.
▍Обработка специфических ошибок, возникающих при выполнении запросов
Вот пример специфического сообщения об ошибке, выводимого в том случае, если пользователь пытается удалить из базы данных город, которого там нет.
Сообщение о специфической ошибке
Тут используется тот же принцип, который мы применяли при работе с глобальными ошибками. Только сведения о таких ошибках хранятся в локальном состоянии соответствующих компонентов. Работа с ними очень похожа на работу с глобальными ошибками. Вот код файла SpecificErrorReq.js.
import React, { Component } from 'react'
import axios from 'axios'
import InlineError from './InlineError'
class SpecificErrorRequest extends Component {
constructor(props) {
super(props)
this.state = {
error: '',
}
this._callBackend = this._callBackend.bind(this)
}
render() {
return (
<div>
<button onClick={this._callBackend}>Delete your city</button>
<InlineError error={this.state.error} />
</div>
)
}
_callBackend() {
this.setState({
error: '',
})
axios
.delete('/api/city')
.then(result => {
// сделать что-нибудь с результатом в том случае, если запрос оказался успешным
})
.catch(err => {
if (err.response.data.error === 'GENERIC') {
this.props.setError(err.response.data.description)
} else {
this.setState({
error: err.response.data.description,
})
}
})
}
}
export default SpecificErrorRequest
Тут стоит отметить, что для сброса специфических ошибок недостаточно, например, просто нажать на некую кнопку x. То, что пользователь прочёл сообщение об ошибке и закрыл его, не помогает такую ошибку исправить. Исправить её можно, правильно сформировав запрос к серверу, например — введя в ситуации, показанной на предыдущем рисунке, имя города, который есть в базе. В результате очищать сообщение об ошибке имеет смысл, например, после выполнения нового запроса. Сбросить ошибку можно и в том случае, если пользователь внёс изменения в то, что будет использоваться при формировании нового запроса, то есть — при изменении содержимого поля ввода.
▍Ошибки, возникающие в клиентской части приложения
Как уже было сказано, для хранения данных о таких ошибках можно использовать состояние тех же компонентов, которое используется для хранения данных по специфическим ошибкам, поступающим с сервера. Предположим, мы позволяем пользователю отправить на сервер запрос на удаление города из базы только в том случае, если в соответствующем поле ввода есть какой-то текст. Отсутствие или наличие текста в поле можно проверить средствами клиентской части приложения.
В поле ничего нет, мы сообщаем об этом пользователю
Вот код файла SpecificErrorFrontend.js, реализующий вышеописанный функционал.
import React, { Component } from 'react'
import axios from 'axios'
import InlineError from './InlineError'
class SpecificErrorRequest extends Component {
constructor(props) {
super(props)
this.state = {
error: '',
city: '',
}
this._callBackend = this._callBackend.bind(this)
this._changeCity = this._changeCity.bind(this)
}
render() {
return (
<div>
<input
type="text"
value={this.state.city}
style={{ marginRight: 15 }}
onChange={this._changeCity}
/>
<button onClick={this._callBackend}>Delete your city</button>
<InlineError error={this.state.error} />
</div>
)
}
_changeCity(e) {
this.setState({
error: '',
city: e.target.value,
})
}
_validate() {
if (!this.state.city.length) throw new Error('Please provide a city name.')
}
_callBackend() {
this.setState({
error: '',
})
try {
this._validate()
} catch (err) {
return this.setState({ error: err.message })
}
axios
.delete('/api/city')
.then(result => {
// сделать что-нибудь с результатом в том случае, если запрос оказался успешным
})
.catch(err => {
if (err.response.data.error === 'GENERIC') {
this.props.setError(err.response.data.description)
} else {
this.setState({
error: err.response.data.description,
})
}
})
}
}
export default SpecificErrorRequest
▍Интернационализация сообщений об ошибках с использованием кодов ошибок
Возможно, сейчас вы задаётесь вопросом о том, зачем нам нужны коды ошибок (наподобие GENERIC), если мы показываем пользователю только сообщения об ошибках, полученных с сервера. Дело в том, что, по мере роста и развития приложения, оно, вполне возможно, выйдет на мировой рынок, а это означает, что настанет время, когда создателям приложения нужно будет задуматься о поддержке им нескольких языков. Коды ошибок позволяют отличать их друг от друга и выводить сообщения о них на языке пользователя сайта.
Итоги
Надеемся, теперь у вас сформировалось понимание того, как можно работать с ошибками в веб-приложениях. Нечто вроде console.error(err) следует использовать только в отладочных целях, в продакшн подобные вещи, забытые программистом, проникать не должны. Упрощает решение задачи логирования использование какой-нибудь подходящей библиотеки наподобие loglevel.
Уважаемые читатели! Как вы обрабатываете ошибки в своих проектах?
Типы ошибок
Последнее обновление: 30.08.2021

В блоке catch мы можем получить информацию об ошибке, которая представляет объект. Все ошибки, которые генерируются интерретатором JavaScript,
предоставляют объект типа Error, который имеет ряд свойств:
-
message: сообщение об ошибке
-
name: тип ошибки
Стоит отметить, что отдельные браузеры поддерживают еще ряд свойств, но их поведение в зависимости от браузера может отличаться:
-
fileName: название файла с кодом JavaScript, где произошла ошибка
-
lineNumber: строка в файле, где произошла ошибка
-
columnNumber: столбец в строке, где произошла ошибка
-
stack: стек ошибки
Получим данные ошибки, например, при вызове несуществующей функции:
try{
callSomeFunc();
}
catch(error){
console.log("Тип ошибки:", error.name);
console.log("Ошибка:", error.message);
}
Консольный вывод:
Тип ошибки: ReferenceError Ошибка: callSomeFunc is not defined
Типы ошибок
Выше мы рассмотрели, что генерируемая интерпретатором ошибка представляет тип Error, однако при вызове несуществующей функции
генерируется ошибка типа ReferenceError. Дело в том, что тип Error представляет общий тип ошибок. В то же время есть конкретные типы ошибок для определенных ситуаций:
-
EvalError: представляет ошибку, которая генерируется при выполнении глобальной функции
eval() -
RangeError: ошибка генерируется, если параметр или переменная, представляют число, которое находится вне некотоого допустимого диапазона
-
ReferenceError: ошибка генерируется при обращении к несуществующей ссылке
-
SyntaxError: представляет ошибку синтаксиса
-
TypeError: ошибка генерируется, если значение переменной или параметра представляют некорректный тип или пр попытке изменить значение, которое нельзя изменять
-
URIError: ошибка генерируется при передаче функциям
encodeURI()иdecodeURI()некорректных значений -
AggregateError: предоставляет ошибку, которая объединяет несколько возникших ошибок
Например, при попытке присвоить константе второй раз значение, генерируется ошибка TypeError:
try{
const num = 9;
num = 7;
}
catch(error){
console.log(error.name); // TypeError
console.log(error.message); // Assignment to constant variable.
}
Применение типов ошибок
При генерации ошибок мы можем использовать встроенные типы ошибок. Например:
class Person{
constructor(name, age){
if(age < 0) throw new Error("Возраст должен быть положительным");
this.name = name;
this.age = age;
}
print(){ console.log(`Name: ${this.name} Age: ${this.age}`);}
}
try{
const tom = new Person("Tom", -45);
tom.print();
}
catch(error){
console.log(error.message); // Возраст должен быть положительным
}
Здесь конструктор класса Person принимает значения для имени и возаста человека. Если передан отрицательный возраст, то генерируем ошибку в виде объекта Error. В качестве параметра в
конструктор Error передается сообщение об ошибке:
if(age < 0) throw new Error("Возраст должен быть положительным");
В итоге при обработке исключения в блоке catch мы сможем получить переданное сообщение об ошибке.
Все остальные типы ошибок в качестве первого параметра в конструкторе также принимают сообщение об ошибке. Так, сгенерируем несколько типов ошибок:
class Person{
constructor(pName, pAge){
const age = parseInt(pAge);
if(isNaN(age)) throw new TypeError("Возраст должен представлять число");
if(age < 0 || age > 120) throw new RangeError("Возраст должен быть больше 0 и меньше 120");
this.name = pName;
this.age = age;
}
print(){ console.log(`Name: ${this.name} Age: ${this.age}`);}
}
Поскольку для возраста можно ередатьне только число, но и вообще какое угодно значение, то вначале мы пытаемся преобразовать это значение в число с помощью
функции parseInt():
const age = parseInt(pAge);
if(isNaN(age)) throw new TypeError("Возраст должен представлять число");
Далее с помощью функции isNaN(age) проверяем, является полученное число числом. Если age — НЕ число, то данная функция возвращает true. Поэтому
генерируется ошибка типа TypeError.
Затем проверяем, что полученное число входит в допустимый диапазон. Если же не входит, генерируем ошибку типа RangeError:
if(age < 0 || age > 120) throw new RangeError("Возраст должен быть больше 0 и меньше 120");
Проверим генерацию исключений:
try{
const tom = new Person("Tom", -45);
}
catch(error){
console.log(error.message); // Возраст должен быть больше 0 и меньше 120
}
try{
const bob = new Person("Bob", "bla bla");
}
catch(error){
console.log(error.message); // Возраст должен представлять число
}
try{
const sam = new Person("Sam", 23);
sam.print(); // Name: Sam Age: 23
}
catch(error){
console.log(error.message);
}
Консольный вывод:
Возраст должен быть больше 0 и меньше 120 Возраст должен представлять число Name: Sam Age: 23
Обработка нескольких типов ошибок
При выполнении одного и то же кода могут генерироваться ошибки разных типов. И иногда бывает необходимо разграничить обработку разных типов.
В этом случае мы можем проверять тип возникшей ошибки. Например, пример выше с классом Person:
class Person{
constructor(pName, pAge){
const age = parseInt(pAge);
if(isNaN(age)) throw new TypeError("Возраст должен представлять число");
if(age < 0 || age > 120) throw new RangeError("Возраст должен быть больше 0 и меньше 120");
this.name = pName;
this.age = age;
}
print(){ console.log(`Name: ${this.name} Age: ${this.age}`);}
}
try{
const tom = new Person("Tom", -45);
const bob = new Person("Bob", "bla bla");
}
catch(error){
if (error instanceof TypeError) {
console.log("Некорректный тип данных.");
} else if (error instanceof RangeError) {
console.log("Недопустимое значение");
}
console.log(error.message);
}
Создание своих типов ошибок
Мы не ограничены встроенными только встроенными типами ошибок и при необходимости можем создавать свои типы ошибок, предназначеные для каких-то конкретных ситуаций.
Например:
class PersonError extends Error {
constructor(value, ...params) {
// остальные параметры передаем в конструктор базового класса
super(...params)
this.name = "PersonError"
this.argument = value;
}
}
class Person{
constructor(pName, pAge){
const age = parseInt(pAge);
if(isNaN(age)) throw new PersonError(pAge, "Возраст должен представлять число");
if(age < 0 || age > 120) throw new PersonError(pAge, "Возраст должен быть больше 0 и меньше 120");
this.name = pName;
this.age = age;
}
print(){ console.log(`Name: ${this.name} Age: ${this.age}`);}
}
try{
//const tom = new Person("Tom", -45);
const bob = new Person("Bob", "bla bla");
}
catch(error){
if (error instanceof PersonError) {
console.log("Ошибка типа Person. Некорректное значение:", error.argument);
}
console.log(error.message);
}
Консольный вывод
Ошибка типа Person. Некорректное значение: bla bla Возраст должен представлять число
Для представления ошибки класса Person здесь определен тип PersonError, который наследуется от класса Error:
class PersonError extends Error {
constructor(value, ...params) {
// остальные параметры передаем в конструктор базового класса
super(...params)
this.name = "PersonError"
this.argument = value;
}
}
В конструкторе мы определяем дополнительное свойство — argument. Оно будет хранить значение, которое вызвало ошибку. С помощью параметра value конструктора
получаем это значение. Кроме того, переопреляем имя типа с помощью свойства this.name.
В классе Person используем этот тип, передавая в конструктор PersonError соответствующие значения:
if(isNaN(age)) throw new PersonError(pAge, "Возраст должен представлять число"); if(age < 0 || age > 120) throw new PersonError(pAge, "Возраст должен быть больше 0 и меньше 120");
Затем при обработки исключения мы можем проверить тип, и если он представляет класс PersonError, то обратиться к его свойству argument:
catch(error){
if (error instanceof PersonError) {
console.log("Ошибка типа Person. Некорректное значение:", error.argument);
}
console.log(error.message);
}
Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером
Ошибки оформления (синтаксиса и линтера)
—
Основы JavaScript
Если программа на JavaScript написана синтаксически некорректно, то интерпретатор выводит на экран соответствующее сообщение, а также указание на файл и строчку в нем, где, по его мнению, произошла ошибка. Синтаксическая ошибка возникает в том случае, когда код был записан с нарушением грамматических правил. В человеческих языках грамматика важна, но текст с ошибками чаще всего можно понять и прочитать. В программировании все строго. Любое мельчайшее нарушение, и программа даже не запустится. Примером может быть забытая ;, неправильно расставленные скобки и другие детали.
Вот пример кода с синтаксической ошибкой:
console.log('Hodor'
Если запустить код выше, то мы увидим следующее сообщение: SyntaxError: missing ) after argument list, а также указание на строку и файл, где возникла эта ошибка. Подобные синтаксические ошибки в JavaScript относятся к разряду SyntaxError.
С одной стороны, ошибки SyntaxError — самые простые, потому что они связаны исключительно с грамматическими правилами написания кода, а не с самим смыслом кода. Их легко исправить: нужно лишь найти нарушение в записи.
С другой стороны, интерпретатор не всегда может четко указать на это нарушение. Поэтому бывает, что забытую скобку нужно поставить не туда, куда указывает сообщение об ошибке.
Ошибки линтера
Теперь, когда мы уже научились писать простые программы, можно немного поговорить о том, как их писать.
Код программы следует оформлять определенным образом, чтобы он был достаточно понятным и простым в поддержке. Специальные наборы правил — стандарты — описывают различные аспекты написания кода. Таких стандартов несколько, самые известные в JavaScript: AirBnb, Standard, Google. В уроках мы будем придерживаться AirBnb.
В любом языке программирования существуют утилиты — так называемые линтеры. Они проверяют код на соответствие стандартам. В JavaScript это eslint.
Взгляните на пример из предыдущего урока:
console.log(8/2+5 - -3 / 2); // => 10.5
Линтер будет «ругаться» на нарушение сразу нескольких правил:
- space-infix-ops – Отсутствие пробелов между оператором и операндами.
- no-mixed-operators – По стандарту нельзя писать код, в котором разные операции используются в одном выражении без явного разделения скобками.
В прошлом уроке мы сами признали, что такое обилие цифр и символов запутывает, и решили добавить скобки исключительно для удобства чтения:
console.log(((8 / 2) + 5) - (-3 / 2)); // => 10.5
Этот вариант уже не нарушает правил, и линтер будет «молчать».
Рассмотрим еще один пример:
console.log(70 * (3 + 4) / (8 + 2));
Есть ли здесь нарушение стандарта?
К сожалению, да. На этот раз операции * и / находятся в одном выражении без разделения скобками. Вы можете решить эту проблему, добавив дополнительные скобки. Но в какой-то момент количество скобок может быть уже настолько большим, что код снова станет неудобным и непонятным. В этот момент разумнее будет разделить выражение на отдельные части. Мы научимся это делать в следующих уроках.
no-mixed-operators — лишь одно из большого количества правил. Другие правила описывают отступы, названия создаваемых сущностей, скобки, математические операции, длину строк и множество иных аспектов. Каждое отдельное правило кажется довольно мелким, не очень важным. Но вместе они составляют основу хорошего кода.
На Хекслете линтер начинает проверять код и сообщать о нарушениях после оформления подписки.
Дополнительные материалы
- Ошибки в JavaScript
- Как найти ошибки в коде
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
-
130 курсов, 2000+ часов теории -
1000 практических заданий в браузере -
360 000 студентов
Наши выпускники работают в компаниях:
21 марта, 2023 12:17 пп
15 views
| Комментариев нет
Java
JavaScript — это язык программирования, который используется во фронтенд- и бэкенд-разработке. Однако иногда правильно расшифровать сообщение об ошибке в JavaScript бывает немного сложно. При устранении проблем в приложени очень важно понимание того, на что ссылается сообщение об ошибке. Современные браузеры поставляются со встроенными утилитами отладки, которые помогают в этом. Каждый браузер по-разному визуально выводит сообщения об ошибках. Но эти сообщения подскажут вам, что происходит с кодом JavaScript.
В этом мануале мы рассмотрим три распространенных типах ошибок JavaScript, которые возникают в среде браузера: ReferenceError, SyntaxError и TypeError. Чтобы следовать этому туториалу, вы должны иметь представление о JavaScript и консоли разработчика.
Читайте также: Использование консоли разработчика JavaScript
Примеры в этом туториале помогут вам ознакомиться с распространенными сообщениями об ошибках. Сообщения в примерах из браузера Chrome, в Firefox или Edge могут содержать немного другую информацию, но типы ошибок одинаковы для всех.
Типы ошибок JavaScript
Ошибки в JavaScript основаны на объекте Error. Это встроенный объект, который содержит информацию о типе возникшей ошибки, за которой следует сообщение с подробным описанием возможной причины. Например, вы можете столкнуться со следующей ошибкой:
VM170:1 Uncaught ReferenceError: shark is not defined at <anonymous>:1:1
Если разобрать это сообщение, вы узнаете, что ReferenceError — это тип обнаруженной ошибки. После двоеточия следует сообщение, которое описывает ошибку: shark is not defined. Последняя строка в этом сообщении указывает, где именно произошла ошибка в коде: 1:1.
Каждый раз, когда браузер обнаруживает ошибку, тип и сообщение об ошибке указывают на проблему. С помощью этой информации можно определить, как отладить код на основе типа ошибки и сообщения, которое вы получите.
Ошибка ReferenceError
ReferenceError возникает, когда вы пытаетесь получить доступ к переменной, которую вы еще не создали. Она также возникает, когда вы вызываете переменную до ее инициализации.
Неопределенные переменные
Например, если вы неправильно написали название переменной при выполнении console.log(), вы получите ошибку ReferenceError:
let sammy = 'A Shark dreaming of the cloud.'; console.log(sammmy);
Вывод будет следующим:
Uncaught ReferenceError: sammmy is not defined at <anonymous>:1:13
Переменная sammmy с тремя буквами m не существует и не определена. Чтобы исправить эту ошибку, можно обновить переменную и написать ее правильно,а затем повторно выполнить команду. Если все прошло успешно, то вы не получите сообщение об ошибке. В целом рекомендуется всегда заранее проверять код на наличие опечаток, чтобы избежать ошибок с неопределенными переменными.
Доступ к переменной до ее объявления
Также вы можете столкнуться с ошибкой при попытке получения доступа к переменной до ее объявления в коде:
function sharkName() { console.log(shark); let shark = 'sammy'; } VM223:2 Uncaught ReferenceError: Cannot access 'shark' before initialization at sharkName (<anonymous>:2:17) at <anonymous>:1:1
В этом примере при выполнении console.log(shark) переменная shark объявлена после вызова, что приводит к ошибке. Как вы сами знаете, сначала нужно объявить переменную, а уже потом пытаться получить к ней доступ.
Примечание: из-за того, как работают объявления let и const, объявления переменных всплывают, но в предыдущем примере они не доступны, потому что находятся в так называемой “временной мертвой зоне” (Temporal Dead Zone). Чтобы избежать этого, рекомендуется объявлять переменные let и const в начале области.
Чтобы исправить эту ошибку, объявите переменную shark перед выполнением команды console.log():
function sharkName() { let shark = 'sammy'; console.log(shark); }
Ошибка ReferenceError часто связана с переменными и областью видимости. Это выходит за рамки данного мануала.
Читайте также: Переменные, области и поднятие переменных в JavaScript
Ошибка SyntaxError
Когда интерпретатор JavaScript просматривает код, он может выдать ошибку SyntaxError, если обнаружит код, который не соответствует спецификации языка. В этом случае ваш код прекратит выполнение и вы получите сообщение о синтаксической ошибке.
Отсутствует закрывающий знак
Например, когда вы пишете функцию и забываете закрыть круглую скобку, ), чтобы заключить свой код, вы получите SyntaxError с очень конкретным сообщением об отсутствующем элементе:
function sammy(animal) { if(animal == 'shark'){ return `I'm cool`; } else { return `You're cool`; } } sammy('shark';
Вы получите:
Uncaught SyntaxError: missing ) after argument list
В сообщении об ошибке указывается отсутствующий символ в коде. В этом примере в вызове функции sammy отсутствует закрывающая скобка ):
. . . sammy('shark');
Отсутствие завершающей фигурной скобки } в конце функции или квадратной ] в массиве также может привести к этой ошибке. Убедитесь, что вы правильно закрываете функции, массивы и объекты.
Одинаковые имена переменных
Вы можете столкнуться с этой ошибкой, когда одно имя используется как параметр функции и внутри ее тела. Рассмотрим следующий пример:
function sammy(animal) { let animal = 'shark'; }
Получаем:
VM132:2 Uncaught SyntaxError: Identifier 'animal' has already been declared
Чтобы исправить эту ошибку, убедитесь, что вы используете уникальные и конкретные имена переменных внутри тела функции. Объявив новое имя переменной, например, animalType, вы устраните конфликт между параметром функции и переменной let в теле:
function sammy(animal) { let animalType = 'shark'; }
Если в теле функции вы собираетесь изменять параметр, не объявляйте переменную с таким же именем. Например, вы можете удалить объявление let внутри тела функции:
function sammy(animal) { animal = 'shark'; }
Убедитесь, что при работе с переменными внутри и вне тела функции вы даете им уникальные имена. При доступе к параметрам функции вы можете использовать их в теле без объявления переменной, например, без let.
Случайные символы
Иногда в коде встречаются опечатки: к примеру, пропущенные символы (как мы уже видели выше) или ненужные повторения символов. Посмотрите на следующий код:
let sharkCount = 0; function sammy() { sharkCount+; console.log(sharkCount); }
Вы получите:
Uncaught SyntaxError: Unexpected token ';'
Дополнительным элементом в этом примере является знак плюс (+) после sharkCount внутри тела функции:
. . . function sammy() { sharkCount++; console.log(sharkCount); }
При возникновении ошибки SyntaxError: Unexpected token проверьте код на наличие пропущенных или дополнительных операторов, таких как знак плюс (+) в примере.
Ошибка TypeError
TypeError возникает, когда значение функции или переменной имеет непредвиденный тип.
Читайте также: Типы данных в JavaScript
Методы массивов и объекты
Одна из распространенных ошибок — использование метода массива для итерации объекта. Например, перебрать объект с помощью метода .map() нельзя, потому что он доступен только для массивов:
const sharks = { shark1: 'sammy', shark2: 'shelly', shark3: 'sheldon' } sharks.map((shark) => `Hello there ${shark}!`);
Вы получите:
Uncaught TypeError: sharks.map is not a function at <anonymous>:1:8
Один из вариантов исправления предыдущего примера — использовать цикл for…in, который работает с объектами:
const sharks = { shark1: 'sammy', shark2: 'shelly', shark3: 'sheldon' } for (let key in sharks) { console.log(`Hello there ${sharks[key]}!`); }
Также можно преобразовать объект sharks в массив, чтобы использовать метод .map():
const sharks = ['sammy', 'shelly', 'sheldon']; sharks.map((shark) => `Hello there ${shark}!`);
Когда вы работаете с разными массивами и объектами, методы легко перепутать. Убедитесь, что метод соответствует типу данных, с которыми вы работаете.
Правильные методы деструктуризации
Аналогично, попытка выполнить итерацию над объектом с деструктуризацией массива приведет к ошибке TypeError:
const shark = { name: 'sammy', age: 12, cloudPlatform: 'sammy' } const [name, age, cloudPlatform] = sharks;
Получаем:
VM23:7 Uncaught TypeError: sharks is not iterable at <anonymous>:7:26
Один из способов решить эту проблему — использовать деструктуризацию объекта, чтобы создавать новые переменные на основе ключей объекта:
const shark = { name: 'sammy', age: 12, cloudPlatform: 'sammy' } const {name, age, cloudPlatform} = shark; console.log(name);
Получим следующий вывод:
sammy
В зависимости от того, как вы структурируете данные, — с помощью массивов или объектов, — убедитесь, что вы используете соответствующие методы для извлечения значений.
Подводим итоги
В этом туториале мы рассмотрели три распространенных типа ошибок JavaScript и некоторые связанные с ними сообщения, а также обсудили, как отладить эти проблемы. Конечно, этот краткий материал сложно назвать исчерпывающим, но вы получили представление о том, как JavaScript кооперируется с браузерами, чтобы указать на проблемы в коде.
Читайте также:
- Методы объектов в JavaScript
- Методы массивов в JavaScript: методы итерации
- Отладка JavaScript с помощью Visual Studio Code и DevTools от Google Chrome
Tags: Javascript
В этой статье мы познакомимся с инструкцией для обработки ошибок try...catch и throw для генерирования исключений.
Непойманные ошибки
Ошибке в коде могут возникать по разным причинам. Например, вы отправили запрос на сервер, а он дал сбой и прислал ответ, который привёл к неожиданным последствиям. Кроме этой, могут быть тысячи других, а также свои собственные.
Когда возникает ошибка, выполнение кода прекращается, и эта ошибка выводится в консоль:
const json = '{name:"Александр"}';
const person = JSON.parse(json); // Uncaught SyntaxError: Unexpected token n in JSON at position 1
console.log('Это сообщение мы не увидим!');
Выполнение этого примера остановится при парсинге строки JSON. В консоль будет выведена непойманная ошибка (uncaught error). Она так называется, потому что мы её не поймали (не обработали). Дальше код выполняться не будет и сообщение, которые мы выводим с помощью console.log() не отобразится.
try…catch
Обработка ошибок в JavaScript осуществляется с помощью try...catch.
try...catch – это специальный синтаксис, состоящий из 2 блоков кода:
try {
// блок кода, в котором имеется вероятность возникновения ошибки
} catch(error) {
// этот блок выполняется только в случае возникновения ошибки в блоке try
}
Первый блок идёт сразу после ключевого слова try. В этот блок мы помещаем часть кода, в котором есть вероятность возникновения ошибки.
Второй блок располагается за ключевым словом catch. В него помещаем код, который будет выполнен только в том случае, если в первом блоке возникнет ошибка. В круглых скобках после catch указываем параметр error. В этот параметр будет помещена ошибка, которая возникла в блоке try.
Код, приведённый выше мы обернули в try...catch, а именно ту его часть, в котором может возникнуть ошибка:
const text = '{name:"Александр"}';
try {
const person = JSON.parse(text); // Uncaught SyntaxError: Unexpected token n in JSON at position 1
} catch(error) {
console.error(error);
console.log(error.message);
}
console.log('Это сообщение мы увидим!');
Здесь в блоке try произойдет ошибка, так как в данном примере мы специально присвоили переменной text некорректную строку JSON. В catch эта ошибка будет присвоена параметру error, и в нём мы будем просто выводить эту ошибку в консоль с помощью console.error(). Таким образом она будет выведена также красным цветом, но без слова Uncaught, т.к. эта ошибка была поймана.
Ошибка – это объект и у него имеются следующие свойства:
message– описание ошибки;name– тип ошибки, например, RangeError при указании значения выходящего за пределы диапазона;stack– строка стека, которая используется в целях отладки; она позволяет узнать о том, что происходило в скрипте на момент возникновения ошибки.
В этом примере мы также написали инструкцию для вывода описание ошибки error.message в консоль с помощью console.log().
Пример функции для проверки корректности JSON:
const isValidJSON = (text) => {
try {
JSON.parse(text);
return true;
} catch {
return false;
}
}
При вызове функции, сначала будет выполняться инструкция JSON.parse(text). Если ошибки не возникнет, то возвратится значение true. В противном случае, интерпретатор перейдёт в секцию catch. В итоге будет возвращено false. Кстати здесь catch записан без указания круглых скобок и параметра внутри них. Эта возможность была добавлена в язык, начиная с версии ECMAScript 2019.
Блок «finally»
В JavaScript возможны три формы инструкции try:
try...catchtry...finallytry...catch...finally
Блок finally выполняется всегда, независимо от того возникли ошибки в try или нет. Он выполняется после try, если ошибок не было, и после catch, если ошибки были. Секция finally не имеет параметров.
Пример с использованием finally:
let result = 0;
try {
result = sum(10, 20);
console.log('Это сообщение мы не увидим!');
} catch(error) {
console.log(error.message);
} finally {
console.log(result);
}
В этом примере произойдет ошибка в секции try, так как sum нигде не определена. После возникновения ошибки интерпретатор перейдём в catch. Здесь с помощью метода console.log() сообщение об ошибке будет выведено в консоль. Затем выполнится инструкция, находящаяся в блоке finally.
В JavaScript имеется также конструкция без catch:
try {
// ...
} finally {
// завершаем какие-то действия
}
Инструкция throw
В JavaScript имеется инструкция throw, которая позволяет генерировать ошибку.
Синтаксис инструкции throw:
throw expression;
Как правило, в качестве выражения обычно используют встроенный основной класс для ошибок Error или более конкретный, например: RangeError, ReferenceError, SyntaxError, TypeError, URIError или другой.
Создаём новый объект Error и выбрасываем его в качестве исключения:
throw new Error('Какое-то описание ошибки');
Пример генерирования синтаксической ошибки:
throw new SyntaxError('Описание ошибки');
В качестве выражения можно использовать не только объект ошибки, но и строки, числа, логические значения и другие величины. Но делать это не рекомендуется:
throw 'Значение не является числом';
При обнаружении оператора throw выполнение кода прекращается, и ошибка выбрасывается в консоль.
Например, создадим функцию, которая будет просто выбрасывать новую ошибку:
// создаём стрелочную функцию и присваиваем её переменной myFn
const myFn = () => {
throw new Error('Описание ошибки');
}
// вызываем функцию
myFn();
console.log('Это сообщение мы не увидим в консоли!');
Для обработки ошибки обернём вызов функции в try...catch:
const myFn = () => {
throw new Error('Описание ошибки');
}
try {
myFn();
} catch(error) {
console.error(error);
}
console.log('Это сообщение мы увидим в консоли!');
В этом примере вы увидите в консоли ошибку и дальше сообщение, которые мы выводим с помощью console.log(). То есть выполнение кода продолжится.
Кроме встроенных классов ошибок можно создать свои собственные, например, путем расширения Error:
class FormError extends Error {
constructor(message) {
super(message);
this.name = 'FormError';
}
}
Использование своего класса FormError для отображение ошибок формы:
<form novalidate>
<input type="text" name="name" required>
<input type="email" name="email" required>
<button type="submit">Отправить</button>
</form>
<script>
class FormError extends Error {
constructor(message) {
super(message);
this.name = 'FormError';
}
}
const elForm = document.querySelector('form');
elForm.onsubmit = (e) => {
e.preventDefault();
elForm.querySelectorAll('input').forEach((el) => {
if (!el.checkValidity()) {
try {
throw new FormError(`[name="${el.name}"] ${el.validationMessage}`);
} catch(error) {
console.error(`${error.name} ${error.message}`);
}
}
});
}
</script>
Глобальная ловля ошибок
Возникновение ошибок, которые мы никак не обрабатываем с помощью try, можно очень просто перехватить посредством window.onerror:
window.onerror = function(message, source, lineno, colno, error) {
// ...
}
Это анонимное функциональное выражение будет вызываться каждый раз при возникновении непойманной ошибки. Ей передаются аргументы, которые мы будем получать с помощью следующих параметров:
message— строка, содержащее сообщение об ошибке;source— URL-адрес скрипта или документа, в котором произошла ошибка;linenoиcolno— соответственно номер строки и столбца, в которой произошла ошибка;error— объект ошибки илиnull, если соответствующий объект ошибки недоступен;
Передача ошибок на сервер
Что делать с этими ошибками? Их, например, можно передавать на сервер для того чтобы позже можно было проанализировать эти ошибки и принять меры по их устранению.
Пример кода для отправки ошибок, возникающих в браузере на сервер через AJAX с использованием fetch:
window.onerror = (message, source, lineno, colno) => {
const err = { message, source, lineno, colno };
fetch('/assets/php/error-log.php', {
method: 'post',
body: JSON.stringify(err)
});
}
На сервере, если, например, сайт на PHP, можно написать такой простенький скрипт:
<?php
define('LOG_FILE', 'logs/' . date('Y-m-d') . '.log');
$json = file_get_contents('php://input');
$data = json_decode($json, true);
try {
error_log('[' . date('d.m.Y h:i:s') . '] [' . $data['message'] . '] [' . $data['lineno'] . ', ' . $data['colno'] . '] [' . $data['source'] . '] [' . $_SERVER['HTTP_USER_AGENT'] . ']' . PHP_EOL, 3, LOG_FILE);
} catch(Exception $e) {
$message = implode('; ', $data);
error_log('[' . date('d.m.Y h:i:s') . '] [' . $message . '] [' . $_SERVER['HTTP_USER_AGENT'] . ']' . PHP_EOL, 3, LOG_FILE);
}
Его следует сохранить в файл /assets/php/error-log.php, а также в этом каталоге создать папку logs для сохранения в ней логов.
В результате когда на клиенте, то есть в браузере будет возникать JavaScript ошибки, они будут сохраняться на сервер в файл следующим образом:













