Почему вы должны справляться со своими ошибками, история из окопов

Есть как минимум две причины, по которым вы хотели бы настроить пользовательскую обработку ошибок в своем JS-проекте: Одна прагматичная, а другая пугающая.

В первом случае могут быть ошибки, которые, как вы знаете, произойдут, и вы просто хотите что-то исправить, когда это произойдет. Допустим, у вас есть компонент, который определенно ломается в очень старых браузерах, и вы просто хотите отключить его всякий раз, когда он дает сбой для какого-то пользователя IE9. Хорошо, настройте для этого обработчик ошибок.

Самый страшный случай - это возникающие ошибки, о которых вы не знаете.

Время рассказа: я собирался на конференцию Strange Loop в Сент-Луисе и заказывал аренду автомобиля на веб-сайте крупной компании по аренде автомобилей. Я не хочу смущать их, называя их имена, поэтому давайте назовем их Пюджетом. После того, как я ввел номер моей кредитной карты, имя и почтовый адрес, регистрационная форма сломалась. Кнопка отправки ничего не сделала. Вот где нормальный человек сдался бы и использовал конкурента. Однако, если вы закаленный программист, которому нужно что-то доказать, это когда вы открываете консоль разработчика. Оказывается, форма молча терпела неудачу, потому что мой почтовый адрес содержал букву «Ø» и, следовательно, не совпадал с /^[a-z0-9 ]+$/i.

Если вы упадете на букву Ø, вы потеряете небольшое количество норвежских клиентов, что может быть не самой большой проблемой в общем плане. Но подумайте о количестве людей в мире, которые пишут на кириллице, ханзи, кана, хангыль… Черт, даже немцы любят время от времени добавлять немного ß, а говорящие по-испански могут иногда использовать ñ. Ограничиваясь [a-z] в качестве компании по аренде автомобилей, вы отвергаете большую часть неанглоязычного мира, а это, вероятно, именно тех людей, которые заинтересованы в аренде автомобиля, когда они находятся в США.

Это именно та глупая ошибка, которую мы все совершили, и все это происходило молча. Клиентам с кредитной картой в руке отказывали, и никто из ответственных за это не знал. Это еще одна причина настроить обработку ошибок, чтобы уведомлять вас, когда что-то неизбежно ломается, чтобы вы могли спать спокойно, зная, что ничего не ломается без вашего ведома.

Итак, как мне обрабатывать ошибки с помощью Vue.js?

Vue имеет несколько довольно простых механизмов для настройки обработчиков ошибок. Однако дьявол кроется в деталях, и оказывается, что при прямом подходе будут упущены определенные классы ошибок. В конце мы опишем решение, позволяющее отловить каждую ошибку, возникающую в рабочем приложении.

Вы должны знать две особенно важные части API Vue.js: errorHandler и warnHandler. Как вы уже догадались, они предназначены для обработки ошибок и предупреждений соответственно. Просто установите эти обработчики перед инициализацией вашего приложения.

Vue.config.errorHandler = function(err, vm, info) { /*your code*/ } Vue.config.warnHandler = function(msg, vm, info) { /*your code*/}

Здесь err - это выданный объект ошибки JavaScript, vm - соответствующий экземпляр Vue, а info - строка, указывающая, в какой части жизненного цикла Vue произошла ошибка. То же самое и с warnHandler, за исключением того, что первый аргумент msg - это строка, содержащая предупреждение.

Что такое ошибка и что такое предупреждение?

Здесь ошибки - это исключения, которые генерируются в JavaScript и не обрабатываются, а предупреждения относятся к проблемам, которые сам Vue обнаруживает во время рендеринга. Vue будет выдавать предупреждения только в режиме разработки, поэтому настройка кода для их обработки может иметь ограниченное применение.

Границы ошибок и errorCaptured

Еще один замечательный механизм, который предоставляет Vue (начиная с версии 2.5), - это errorCaptured, который позволяет выявлять ошибки на уровне компонентов. Это, в свою очередь, позволяет реализовать границы ошибок. Вы можете настроить errorCaptured при определении вашего компонента:

Vue.component('parent', {
  template: '<div><slot></slot></div>',
  errorCaptured: (err, vm, inf) => alert('I have a broken child :(')
})
Vue.component('child', {
  template: '<h1>{{ fail() }}</h1>'
})

Затем вы можете использовать эти компоненты следующим образом:

<parent>
   <child></child> 
</parent>

Родительский компонент перехватит ошибки дочернего компонента. Обратите внимание, что errorCaptured будет только перехватывать ошибки в дочерних компонентах, а не в самом компоненте. Таким образом, родитель будет видеть недостатки своих детей, но не свои недостатки, как в вашем воспитании.

Если функция errorCaptured возвращает false, она не распространяет ошибку до своего родительского компонента. Это позволяет нам создать общий механизм остановки ошибки, называемый границей ошибки.

Vue.component('error-boundary', {
    template: '<div><slot></slot></di>',
    errorCaptured: (err, vm, info) => {
        console.log('We have an error');
        return false;
    }
})

Теперь у нас есть общий механизм, в который мы можем заключать компоненты.

<error-boundary>
    <something-that-can-fail></something-that-can-fail>
</error-boundary>

Это похоже на попытку разметки для выявления проблем, возникающих во время рендеринга.

Но подождите: это не обработает все ошибки!

errorCaptured, сможешь ли ты с этим справиться?
warnHandler, ты справишься?
errorHandler, ты справишься?
Не думаю, что они с этим справятся!

- Destiny’s Child, первые сторонники создания глобального обработчика ошибок для ошибок, которые происходят вне процесса рендеринга Vue.

У всего этого есть проблема: он не улавливает все ошибки. По сути, он выявляет только те ошибки, которые может увидеть код Vue. Vue - это не тоталитарный фреймворк, который охватывает все, что вы делаете, поэтому все еще есть ошибки, которые эти обработчики никогда не поймают. Как и покемоны, мы хотим поймать их всех. Наша храбрость вытащит нас. Ты учишь меня, а я учу тебя.

Ошибки, происходящие вне процесса рендеринга Vue, не будут обнаружены. Например, если вы привязываете функцию к событию щелчка, и ваша функция создает ошибку, она не будет перехвачена ни одним из вышеперечисленных механизмов. Обычно эти обработчики не перехватывают ошибки, возникшие в вашем коде.

Как на самом деле отловить каждую ошибку

Чтобы действительно отловить каждую отдельную ошибку, вам необходимо настроить глобальный обработчик ошибок в браузере, назначив функцию свойству window.onerror.

window.onerror = function(msg, src, linenum, colnum, error) { 
    /*your code*/ 
}

Это будет вызываться для всех неперехваченных ошибок. Здесь msg - это сообщение об ошибке, src - это URL-адрес файла, в котором произошла ошибка, linenum и colnum - это номер строки и символа, в котором произошла ошибка, а error - объект ошибки. Однако имейте в виду, что этот API - беспорядок с точки зрения несоответствий браузера.

  • Объект ошибки не передается в IE10, Safari 9 и браузере Android.
  • colnum не передается в IE8 и 9.
  • Свойство error.stack очень полезно, но нестандартно и содержит разную информацию от браузера к браузеру.

Также имейте в виду, что ошибки, возникающие в сценариях, загруженных из другого источника, будут отображаться только как Ошибка сценария, если вы не позаботитесь о настройке CORS.

Тем не менее, если вы позаботитесь о решении этих проблем, у вас будет надежный механизм обработки ошибок, который вызывается всякий раз, когда возникает необработанная ошибка.

Как регистрировать каждую ошибку с помощью CatchJS

Если вы хотите перехватывать каждую ошибку, возникающую на стороне клиента, и постоянно регистрировать ее на сервере, вы можете добиться этого, просто добавив скрипт CatchJS.

<script src="//cdn.catchjs.com/catch.js"></script>

Вам понадобится учетная запись в сервисе, но если вы используете Vue.js, вам не нужно вносить какие-либо изменения в свой код. Простое добавление этого скрипта установит глобальный обработчик ошибок в браузере и автоматически регистрирует все ошибки вместе с телеметрией, необходимой для воспроизведения обстоятельств ошибки. Эта опция хороша тем, что она может дать вам след кликов, который приводит к ошибке, и снимки экрана того, как выглядел экран, когда возникла проблема. Это не зависит от фреймворка, поэтому он так же хорошо работает с Angular, React и всем остальным, что можно использовать в интерфейсе.

Помните, друзья не позволяют друзьям сбрасывать неизвестные исключения, предварительно не зарегистрировав их. Будьте в безопасности!

Первоначально опубликовано на catchjs.com.