
В этой статье я рассмотрю вопрос выявления и обработки ошибок, возникающих на фронтенде (браузер или web-view).
На фронтенде код JS выполняется в браузере. JavaScript не является компилируемым языком, поэтому всегда существует вероятность ошибки исполнения при непосредственном использовании программы. Ошибка исполнения блокирует код, расположенный после места ошибки, и пользователи программы рискуют остаться с нефункциональным экраном приложения, который можно будет лишь перезагрузить или закрыть. Но есть методы выявления ошибок и их безопасного сопровождения, позволяющие избежать таких ситуаций.
1. Средства языка JavaScript
Блоки try/catch
Функции, которые могут выполниться с ошибкой, оборачивают в блоки try/catch. Сначала программа пытается выполнить код в блоке try. Если по каким-то причинам выполнение кода сломалось, программа переходит в блок catch, где доступны три параметра:
- name — стандартизированное имя ошибки;
- message — сообщение о деталях ошибки;
- stack — текущий стек вызова, в котором произошла ошибка.
То есть:
try { callFunc(); } catch (e) { console.log(e.name) // ReferenceError console.log(e.message) // callFunc is not defined console.log(e.stack) // ReferenceError: callFunc is not defined at window.onload }
Для разработчика главное то, что программа сможет продолжить выполнение после блока catch. Таким образом, взаимодействие с пользователем не прервется.
С помощью блока try/catch также можно вызывать собственные ошибки, например при проверке данных:
const user = { name : «Mike» }; try { if (!user.age) throw new SyntaxError(«User age is absent!»); } catch (e) { console.log(e.name) // SyntaxError console.log(e.message) // User age is absent! console.log(e.stack) // SyntaxError: User age is absent! at window.onload }
Наконец, можно расширить эту инструкцию еще одним блоком — finally, который выполняется ��сегда: и в случае, если в try не было ошибки, и в случае, если управление перешло в блок catch:
try { callFunc(); } catch (e) { console.log(e) } finally { ... }
Иногда используют инструкцию try/finally (без catch), чтобы была возможность продолжать использование кода без обработки конкретных ошибок.
Событие window.onerror
Часто бывает полезно знать, что скрипт на странице сломался, даже если программа сломалась и пользовательская сессия закончилась неудачно. Обычно эту информацию потом используют в системах логирования ошибок.
У глобального объекта window существует событие onerror (использовать его надо аккуратно: в разных браузерах реализация может отличаться!):
window.onerror = function(message, url, line, col, error) { console.log(`${message}\n В ${line}:${col} на ${url}`); };
Если разместить этот код в начале скрипта или подгрузить отдельным скриптом в первую очередь, то при любой ошибке ниже разработчику будет доступна подробная информация о ней.
Однако полная информация доступна только для скриптов, которые были загружены с того же домена. Если сломанный скрипт загружен с другого домена, window.onerror сработает, однако подробностей ошибки не будет.
Компоненты фреймворков
Некоторые JS-фреймворки (React, Vue) предлагают собственные решения по обработке ошибок. Например, React сможет отрисовать специальную верстку на месте блока, в котором произошла ошибка:
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // Обновить состояние с тем, чтобы следующий рендер показал запасной UI. return { hasError: true }; } componentDidCatch(error, errorInfo) { // Можно также сохранить информацию об ошибке в соответствующую службу журнала ошибок logErrorToMyService(error, errorInfo); } render() { if (this.state.hasError) { // Можно отрендерить запасной UI произвольного вида return <h1>Что-то пошло не так.</h1>; } return this.props.children; } } <ErrorBoundary> <MyWidget /> </ErrorBoundary>
Фактически, компонент React оборачивается специальным компонентом, который обрабатывает ошибки. Это напоминает оборачивание функций с помощью конструкции try/catch.
2. Средства сборки проекта
Современные JS-скрипты, как правило, делаются транспилируемыми. То есть разработка ведется с использованием последних стандартов ES. А затем код разработчика с помощью сборщика проектов (такого как Webpack) преобразуется в код, который будет гарантированно работать в выбранном числе браузеров.
На этапе сборки код проверяется на верность синтаксиса. Незакрытая скобка или неправильное обозначение поля класса немедленно вызовут ошибку при сборке, и бандл просто не соберется. Разработчику придется сразу исправить такие ошибки, чтобы работать дальше.
Также сборщик может подсказать, что какие-то куски кода не используются при выполнении программы. Возможно, это натолкнет разработчика на мысль о более глубоком изучении кода, что косвенно может повлиять на выявление им новых ошибок.
3. Тестирование
Еще один способ не допустить ошибок в коде – тестировать его. Во фронтенде есть инструменты для эффективного использования юнит-тестов. Обычно используют фреймворки, такие как Jest, Karma, Mocha, Jasmine. Вместе с тестовыми фреймворками часто используют расширения, такие как Enzyme, React Testing Library, Sinon и другие, позволяющие обогатить тесты с помощью мокирования, функций-шпионов и других инструментов.
При поиске ошибок тесты в первую очередь полезны нагрузкой разнообразными данными, которые могут привести к ошибкам исполнения. Так, следующий код пройдет валидацию синтаксиса и сработает ожидаемо:
const func = (data) => { return JSON.parse(data) } func('{«a»:1}')
Однако он сломается, если подать ему неверное значение:
func() // Uncaught SyntaxError: Unexpected token u in JSON at position 0.
Этот код тоже проходит валидацию при сборке:
const obj = { outer : { last : 9 } } if (obj.outer.inner.last) { console.log(«SUCCESS») }
Однако он также сломается во время исполнения. После тестирования разработчик наверняка сделает дополнительные проверки:
if (obj.outer?.inner?.last) { console.log(«SUCCESS») }
Часто подобные ошибки возникают при получение данных от сервера (например AJAX-запросом) с их последующим разбором. Тестирование кода позволяет выявить и заранее устранить случаи, когда код может сломаться во время исполнения в браузере клиента.
4. Логирование
Допустим, мы предприняли все возможные меры по недопущению ошибок во время разработки и сборки проекта. Однако ошибки все-равн�� могут проникать в продуктивный код. Нам требуется как-то узнавать об их наличии и принимать скорые меры по исправлению. Просить пользователей открывать консоль браузера и делать скриншоты — не самый лучший вариант. Поэтому к проекту неплохо подключить логирование ошибок.
Смысл простой: на каждое событие window.onerror или в каждый переход исполнения кода в блок catch выполняется простой AJAX-запрос на специально выделенный адрес сервера, в тело которого кладется информация об ошибке. Далее потребуется инструмент, который быстро оповестит техподдержку и разработчиков о наличии новых ошибок и позволит эффективно работать с ними. Самый популярный из таких инструментов для фронтенда — Sentry.
Система логирования Sentry позволяет собирать, группировать, представлять ошибки в реальном времени. Есть сборки для разных языков, в том числе JavaScript. Проект предоставляет платный доступ с расширенными возможностями для бизнеса, однако можно попробовать его основные возможности на бесплатном тестовом аккаунте.
Подключать Sentry можно как непосредственно в HTML-файле, так и в компонентах, выполненных на одном из популярных фреймворков: React, Vue, Angular, Ember и других.
Для подключения возможностей логирования прямо в браузере в секции загружаем скрипт:
<script src=«https://browser.sentry-cdn.com/5.13.0/bundle.min.js» integrity=«sha384-ePH2Cp6F+/PJbfhDWeQuXujAbpil3zowccx6grtsxOals4qYqJzCjeIa7W2UqunJ» crossorigin="anonymous"></script>
Далее в коде JS инициализируем:
Sentry.init({ dsn: 'https://<your account key here>@sentry.io/<your project id here>' });
Всё. Если и когда в коде ниже этой строчки произойдет ошибка, Sentry выполнит ее логирование. Логи будут записаны даже тогда, когда ошибка произошла по вине скриптов с других доменов:


Sentry имеет широкие возможности по анализу массива сообщений об ошибках и настройке уведомлений. Также возможна группировка логов ошибок по релизам вашего продукта:
Sentry.init({ dsn: 'https://<your account key here>@sentry.io/<your project id here>', release: '2020.03.06.1' });
С помощью Sentry в статистику можно передать контекст ошибки, например, информацию о клиенте, fingerprint, уровень ошибки (fatal, error, warning, info, debug), проставить теги.
Есть возможность записывать в статистику пользовательские события. Например, можно поставить на отслеживание изменение размеров окна браузера или совершение AJAX-запроса. Также у Sentry есть собственный виджет с окном обратной связи, которое можно показать пользователю при ошибке. Это даст дополнительную информацию для расследования обстоятельств возникновения ошибки.
Для развертывания Sentry вместе с фреймворками достаточно просто установить пакет и подключить:
# Using yarn yarn add @sentry/browser # Using npm npm install @sentry/browser
Делаем инициализацию в главном скрипте проекта (для React и Angular):
import * as Sentry from «@sentry/browser»; Sentry.init({ dsn: 'https://<your account key here>@sentry.io/<your project id here>' });
Для Vue и Ember передаем еще одну обязательную строку конфигурации:
# Vue Sentry.init({ dsn: '<your account key here>@sentry.io/<your project id here>', integrations: [new Integrations.Vue({Vue, attachProps: true})], }); # Ember Sentry.init({ dsn: '<your account key here>@sentry.io/<your project id here>', integrations: [new Integrations.Ember()] });
Пакет integrations устанавливается отдельно:
# Using yarn yarn add @sentry/integrations # Using npm npm install @sentry/integrations
Для предотвращения конфликтов и дублирования информации при подключении нескольких скриптов в одном проекте Sentry позволяет создавать отдельного клиента для каждой инициализации логирования:
import { BrowserClient } from «@sentry/browser»; const client = new BrowserClient({ dsn: '<your account key here>@sentry.io/<your project id here>', }); client.captureException(new Error('example'));
На сайте проекта есть подробная документация с примерами использования: https://docs.sentry.io.
Статья подготовлена при поддержке облачной платформы Mail.ru Cloud Solutions.
Что еще почитать по теме:
