Погрузитесь в ошибки/исключения интерфейса

JavaScript

предисловие

Я всегда придерживался такой точки зрения: с определенной точки зрения люди живут в мире, полном ошибок. Неправильное загрязнение окружающей среды, неправильное городское проектирование (подземная канализация, проектирование дорог), неправильное утилитарное общество, неправильный способ сравнения, неправильное сознание, неправильное отношение, неправильные действия... Одним словом, люди совершают ошибки, и ошибки в этом повсюду. Мир. Это основной факт.

То же самое можно применить и к области программирования. Точно так же мир программирования полон ошибок. Некоторые ошибки вызваны внешними обстоятельствами, а некоторые ошибки вызваны собственной небрежностью. Одна вещь, которая отличается от реального мира, заключается в том, что мы, разработчики, не боимся делать ошибки в мире программного обеспечения, точнее, мы не боимся совершать небольшие ошибки. Потому что мы всегда можем передать такие ошибки пользователю во время выполнения, с глаз долой и из памяти. Очевидно, что такое отношение и подход непрофессиональны. В разработке программного обеспечения обеспечение надежности программного обеспечения является одной из тем. Таким образом, способность правильно обрабатывать ошибки внешнего интерфейса является отражением уровня разработки программного обеспечения разработчиков внешнего интерфейса. Я помню, как один из моих друзей по back-end разработке как-то сказал мне: «Сэм, разве ты не работаешь с исключениями во front-end разработке? Ты слишком непрофессионален». Да, по сравнению с важностью, которую серверные приложения придают обработке ошибок/исключений, интерфейсные приложения определенно далеки от этого. Поэтому, чтобы отразить наш профессионализм как инженеров-программистов, мы должны уделять внимание обработке ошибок/исключений во внешнем интерфейсе и обрабатывать их должным образом.

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

Обработка исключений внешнего интерфейса не только отражает наш профессионализм как инженеров-программистов, но и позволяет нам подключаться к системе мониторинга внешнего интерфейса. С развитием многофункциональных клиентских приложений и разделением моделей разработки программного обеспечения для клиентских и серверных частей масштаб внешнего кода также увеличивается. Чтобы лучше отслеживать проблему, локализовать проблему и решить проблему, появилась система внешнего мониторинга. В настоящее время передняя система мониторинга берет на себя две основные функции: «мониторинг ошибок» и «мониторинг производительности». «Мониторинг производительности» не входит в содержание этой статьи, поэтому он здесь не указан. Если мы хотим собирать фронтальные ошибки в системе фронтенд-мониторинга сервера, то для фронтенда нам предстоит столкнуться с проблемой «как найти и сообщить об ошибках во фронтенде?».

Внешнее приложение — это место, где вся программная служба находится ближе всего к пользователям.Основываясь на бизнес-принципе «клиент — это Бог», разработчики внешнего интерфейса несут ответственность за создание хорошего пользовательского опыта для пользователей. Когда на странице возникает ошибка, это, несомненно, более удобный способ напомнить пользователю о том, что произошло в нужное время, соответствующим образом, чем если бы страница не перемещалась.

Подводя итог, у нас достаточно причин и мотивации, чтобы хорошо справляться с ошибками фронтенда. Теперь давайте начнем.

текст

тип ошибки

Потому что продуктом веб-интерфейса является веб-страница. Таким образом, ошибки внешнего интерфейса — это ошибки страницы. Итак, с какими ошибками в повседневной разработке могут столкнуться наши страницы? Могу выделить следующие виды:

  1. ECMAScript exceptions
  2. DOMException and DOMError
  3. ошибка загрузки статического веб-ресурса
  4. Ошибка скрипта, вызванная междоменной ссылкой на скрипт
  5. сбой страницы

1. ECMAScript exceptions

ECMAScript — это ошибка, возникающая в процессе выполнения. Каждая ошибка имеет соответствующий тип ошибки. При возникновении ошибки будет выдан соответствующий тип объекта ошибки. Ниже приведены семь неправильных типов, определенных ECMA-262, и нестандартныйInternalErrorТипы.

  • Error

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

  • EvalError

    Эта ошибка возникает, если eval() не вызывается как функция. Например:

    new eval();
    eval = foo;
    

    Теоретически браузеры должны выдавать EvalError. Но на самом деле каждый браузер ведет себя по-разному. Ввиду того, что EvalError больше не используется в текущей спецификации ECMAScript, этот тип ошибки встречается редко, и предполагается, что движок js не будет выдавать этот тип ошибки в будущем.

  • RangeError

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

    const foo = new Array(-1);
    const bar = new Array(Number.MAX_VALUE);
    
    // 无限递归,突破了call stack的maxmium size
    function foobar(){
        foobar();
    }
    foobar();
    
  • ReferenceError

    Этот тип ошибки обычно возникает при доступе к необъявленным или несуществующим переменным. Например:

    console.log(a); // a并没有声明就访问了的情况下,浏览器会报ReferenceError
    

    Этот тип ошибки возникает, когда мы пишем неправильное имя переменной.

  • SyntaxError

    Как следует из названия, этот тип ошибки возникает, когда в нашем коде javascript есть синтаксическая ошибка. Например:

    eval( 1 ++ 2); // 多写了一个加号
    
    // 关键字写错了
    constt a = 1;
    function test(){
        retrun '应该是r-e-t-u-r-n才对';
    }
    
  • TypeError

    Это тип ошибок, который мы чаще всего видим во время выполнения. Поскольку javascript динамически слабо типизирован, типы данных всех переменных могут быть изменены. Если во время выполнения над переменной с неподходящим типом данных выполняется недопустимая операция, браузер сообщит об известной ошибке TypeError. Например:

    const o = new 10; // TypeError
    for(let key in 10){} // TypeError
    (10).slice() // TypeError
    
  • URIError

    При использовании encodeURI() или decodeURI() будет вызвана ошибка URIError, если формат URL-адреса входящего символа неверен. Однако на самом деле такого рода ошибки встречаются редко, поскольку отказоустойчивость этих двух функций очень высока.

  • InternalError

    Это нестандартизированный тип ошибки. Это относится к тем ошибкам, которые возникают в движке javascript. Обычно это ошибка, вызванная слишком большим количеством чего-либо. Например:

    • Мы написали слишком много операторов switch...case...;
    • Слишком много скобок в регулярном выражении и т. д.

2. DOMException and DOMError

согласно сПрофиль на MDN, DOMError не является стандартной спецификацией, здесь нет необходимости ее обсуждать. DOMException — это ошибка, возникающая при вызове метода или свойства веб-API, или просто ошибка, возникающая при выполнении операций DOM.

При работе со звуком этот тип ошибки часто возникает, когда метод веб-API вызывается в неподходящее время. Например:

window.onload = function () {
    const video = document.getElementById('video');
    video.play(); 
};


<video id="video" preload="none" src="http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4"></video>

Приведенный выше код сообщит об исключении DOMException:

Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first.

Другой пример — ошибочный вызов интерфейса DOM:

<head>
    <script type="text/javascript">
        function ThrowDOMException () {
            var elem = document.createAttribute ("123");
        }
    </script>
</head>
<body>
    <button onclick="ThrowDOMException ()">Throw a DOM exception</button>
</body>

В приведенном выше примере браузер сообщит об ошибке DOMEXCEPTION:

Uncaught DOMException: Failed to execute 'createAttribute' on 'Document': The localName provided ('123') contains an invalid character.

3. Статические ресурсы Network Load Error

Общие статические сетевые ресурсы включают файлы html, файлы css, файлы javascript, изображения, аудио, видео, iframe и так далее. Все статические веб-ресурсы подвержены ошибкам загрузки. Причина ошибки загрузки неизвестна, может быть адрес неправильно написан, на сервере может вообще не быть ресурса, может быть внутренняя ошибка на сервере, или сеть может быть занята, а ресурс загрузка не удалась из-за тайм-аута запроса. Независимо от причины, если клиент не загружается успешно, это будет считаться ошибкой загрузки.

4. script error

Когда на ресурс javascript в другом домене ссылаются из разных доменов, а выполнение javascript завершается сбоем, браузер выдает эту ошибку.

Предположим, у нас есть такой файл js под доменным именем other-domain.com:

const a = {};
console.log(a.b.c);

В нашем собственном домене origin-domain.com мы переходим к ссылке на этот файл:

<script src="http://another-domain.com/index.js"></script>

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

5. page crash

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

Ошибочное предупреждение

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

DOMException обычно является ошибкой, сделанной вашим пониманием или памятью DOM API. Прежде чем ошибиться, вы подсознательно думаете, что можете быть правы. Так что в этом случае искусственно предотвратить невозможно. Для других видов ошибок источник ошибки практически не поддается вашему контролю, поэтому вы не можете его предотвратить. В этом разделе я в основном обсуждаю, как мы можем предотвратить исключения ECMAScript этого типа ошибок, которые мы можем предотвратить искусственно. На самом деле, после внедрения typescript и добавления слоя защиты типов в наш javascript многие ошибки в нем можно вспомнить и исправить в период написания кода. Тем не менее, в этом разделе мы по-прежнему хотим поговорить о том, как предотвратить ошибки такого типа, выработав хорошие привычки программирования на JavaScript.

Среди типов ошибок в исключениях ECMAScript мы в основном защищаемся от следующих трех подтипов ошибок:

  • Ошибка, вызванная неявным преобразованием типов
  • Ошибки из-за отсутствия защиты типа
  • Ошибка связи

Ошибка, вызванная неявным преобразованием типов

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

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

this.list = this.list.map(item=> {
    if(item.id == myID){
        return {
            ...item,
            iSelected: true
        }
    }
    
    return item;
})

Да, для отказоустойчивости также используется неявное преобразование типов с помощью знака равенства вместо полного знака равенства. Итак, в неявном преобразовании типов нет ничего плохого, ключ в том, что вы знаете, как его использовать. Но опять же, неявное преобразование типов в конечном итоге снизит предсказуемость кода, что затруднит его сопровождение. Личное мнение, даже если вы знаете, что используете неявные приведения, лучше их не использовать. Приведенный выше код можно преобразовать с помощью принуждения:

if(parseInt(item.id) === parseInt(myID)){
    ......
}

Как мы все знаем, при использовании операторов равенства (==) и неравенства (!==) или при использовании небулевых данных в операторах управления потоком, таких как if, for и while, мы, по сути, используем неявное преобразование типов..

Стоит добавить, что при значениях свойства используют непрямовый тип ключа для доступа к объекту, произойдут неявное преобразование типа, например:

const obj = {1:'implicitly convert'};
obj[1] === obj['1'] === obj[1.0] === obj[+1]; // true


// 对象类型转换为字符串类型,首先会从这个对象自身开始,沿着原型链上去查找toString方法。
var o = { toString: function () { return "-1.5"; } }; 

obj[-1.5] === obj[o];// true

Когда ваша бдительность в отношении неявных приведений немного упадет, вы совершите ошибку. Например, в развитии бизнеса по управлению капиталом у нас есть поле ввода, которое в основном используется для ввода суммы покупки. Под полем ввода у нас будет подсказка. Следите за вводом пользователя в режиме реального времени и синхронно вычисляйте проценты, генерируемые суммой покупки:

let hintText = '请输入购买金额';
const purchaseAmount =parseFloat(document.getElementById('input').value) ;

if(purchaseAmount) {
    const income = .... // 做算术四则运算;
    hintText = `预计收益是$(income)元`;
}

Поскольку в настоящее время ввод пользователя может быть "0", поскольку неявный тип оператора if преобразуется в false, hintText не может быть обновлен. В результате, несмотря на то, что пользователь что-то ввел, в интерфейсе по-прежнему отображается приглашение по умолчанию «Пожалуйста, введите сумму покупки». Хотя это и не фатальная ошибка, это явно нелогичная ошибка.

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

if(typeof purchaseAmount === 'number' && !isNaN(purchaseAmount)){
    // ......
}

Хотя ошибка здесь не вызовет ошибку времени выполнения js-кода, в следующий раз будет просачиваться неподходящий тип в блок кода оператора if, а затем выполняться неподходящие операции на основе данных этого типа. ошибка TypeError.

Итак, мы хотим избежать использования неявных преобразований. Как обойти закон?

  • Используйте конгруэнтные (===) и неравные (!==) знаки вместо равенства (==) и неравенства (!=);
  • В различных операторах управления потоком убедитесь, что логическое значение (или выражение, значение которого является логическим значением) передается при вынесении условных суждений.

Ошибки из-за отсутствия защиты типа

JavaScript — динамически слабо типизированный язык программирования. Поэтому в процессе использования js для разработки ПО для обеспечения надежности программы слишком важна защита типов.

Например, давайте реализуем функцию, которая получает строку запроса из URL-адреса:

function getQueryString(url){
    const pos = url.indexOf('?');
    if (pos > -1) {
        return url.slice(pos + 1);
    }
    
    return '';
}

Мы считаем само собой разумеющимся, что URL имеет строковый тип. Но что, если человек, вызвавший нашу функцию, случайно передал числовое значение? Очевидно, что здесь вызывается неподходящий метод для неподходящего типа данных, и браузер во время выполнения выдает нам большую ошибку «Uncaught TypeError: url.indexOf is not a function». Этой ошибки можно избежать, если мы будем бдительны и добавим простое утверждение для защиты типа:

function getQueryString(url){
    if(typeof url === 'string') {
        const pos = url.indexOf('?');
        if (pos > -1) {
            return url.slice(pos + 1);
        }
    }
    
    return '';
}

В защите типа, как подготовиться к определению типа данных? Я суммирую это двумя способами.

Первый - судить по родному оператору. длябазовый тип(Примитивный тип данных) Например, нам нужно использовать только оператор TypeOf, чтобы оценить:

typeof undefined === 'undefined' // true
typeof 'str' === 'string' // true
typeof 123 === 'number' // true
typeof true === 'boolean' // true
typeof Symbol('react') === 'symbol' // true

Здесь стоит упомянуть, что null немного особенный, в конце концов, это ошибка на миллиард долларов. Значение typeof null является «объектом». Таким образом, мы можем судить о следующем:

// 直接判断
const n = null;
n ===  null // true 

又或者采用jquery里面的写法:
String(n) === 'null' // true

Для типов объектов недостаточно точно использовать оператор typeof для суждения, мы должны использовать оператор instanceof для суждения:

const obj = {};
const fn = ()=> {};
const arr = [];
const date = new Date();
const reg = /^[a-z]$/;
const promise = new Promise(reslove=> reslove())


obj instanceof Object // true
fn  instanceof Function // true
arr instanceof Array // true
date instanceof Date // true
reg instanceof RegExp // true
promise instanceof Promise // true

Второй — универсальный подход с небольшой хитростью:

function typeOf(obj){
    const stringResult = Object.prototype.toString.call(obj);
    const matchResult = stringResult.match(/^\[(\w+)\s+(\w+)\]$/);
    
    if(matchResult !== null){
        return matchResult[2].toLowerCase();
    }
    
    throw new Error('未知类型');
}

const obj = {};
const fn = ()=> {};
const arr = [];
const date = new Date();
const reg = /^[a-z]$/;
const promise = new Promise()

typeOf(null) === 'null' // true
typeOf(undefined) === 'undefined' // true
typeOf(1) === 'number' // true
typeOf('hello world') === 'string' // true
typeOf(true) === 'boolean' // true
typeOf(Symbol('react')) === 'symbol' // true
const promise = new Promise(reslove=> reslove())

typeOf(obj) === 'object' // true
typeOf(fn) === 'function' // true
typeOf(arr) === 'array' // true
typeOf(date) === 'date' // true
typeOf(reg) === 'regexp' // true
typeOf(promise) === 'promise' // true

Видно, что этот метод typeOf может точно определить свой тип для всех значений javascript. Это необходимое лекарство для «домашних путешествий, убийства людей».

Ошибка связи

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

При общении между внешним интерфейсом и клиентом обычно бывает так, что внешний интерфейс передает клиенту незакодированный URL-адрес, в результате чего клиент анализирует ошибку. Например:

ssj://login?redir=https://frontend.com/index.html?a=b&c=d

Для вышеуказанной проблемы мы можем использовать метод encodeURIComponent() для кодирования строки после «redir», чтобы решить эту проблему:

ssj://login?redir=https%3A%2F%2Ffrontend.com%2Findex.html%3Fa%3Db%26c%3Dd

Клиентская сторона также должна использовать метод сопоставления для декодирования перед синтаксическим анализом.

В процессе общения с сервером есть много моментов, на которые стоит обратить внимание. Во многих случаях front-end и back-end указали тип и структуру данных через интерфейсный документ, но back-end все равно ошибается и не возвращает тип и структуру данных согласно договоренности.

Что касается типов данных, распространенные ошибки, допускаемые серверной частью:

  • Путаница между числовыми и строковыми типами
  • Путаница нуля и «нуль»

Что касается структуры данных, то бэкенд часто не возвращается по договоренности. Например, структура данных, которую мы договорились вернуть, выглядит следующим образом:

{
    success: true,
    msg: '',
    data: {
        list: []
    }
}

В итоге он мне вернул:

{
    success: true,
    data: {
        msg: '',
        data: []
    }
}

Другой причиной является синтаксическая проблема в возвращаемой строке последовательности json, из-за которой внешний интерфейс использует JSON.stringify() для синтаксического анализа и сообщения об ошибке.

Таким образом, если мы слишком доверяем данным, возвращаемым взаимодействующей стороной, и не защищаем соответствующие типы и поля, то наш js-код, скорее всего, сообщит об ошибке TypeError во время выполнения.

обработка ошибок

Хотя при хорошем чутье на профилактику и с помощью Typescript многие низкоуровневые ошибки можно задушить еще в колыбели. Однако самый плохой план, в конце концов, хороший. Неважно, совершите ли вы ошибку, важно знать, как с ней справиться. Как с этим бороться? Я разбиваю его на два этапа:

  • Первый шаг: поймать исключение и получить информацию об ошибке;
  • Второй шаг: в соответствии с фактическими потребностями и соответствующей обработкой;

Шаг 1. Перехватите исключение и получите сообщение об ошибке

На странице техническими средствами отлова исключений являются не что иное, как следующее:

  • try...catch
  • window.onerror
  • window.addEventListener('error',()=>{})
  • element.onerror
  • Promise Catch и window.addEventListener("unhandledrejection",()=> {})
  • iframe и iframe.onload
  • разное

Ниже приведено подробное объяснение.

try...catch
  1. мотивация

    Есть два основных мотива использования try...catch для перехвата исключений: 1) я действительно хочу перехватывать исключения в коде, которые могут вызвать ошибки; 2) я хочу убедиться, что код, стоящий за этим, продолжает работать.

    О первой мотивации говорить нечего, здесь мы говорим о второй мотивации. Предположим, у нас есть следующий код:

    console.log(foo);
    console.log('I want running')
    

    Как только код выполнится, угадайте, что? Оператор первой строки сообщает об ошибке, а журнал оператора второй строки не печатается. Если мы изменим код на этот:

    try{ 
        console.log(foo)
    }catch(e){
        console.log(e)
    }
    console.log('I want running');
    

    После выполнения приведенного выше кода, несмотря на то, что сообщение об ошибке ReferenceError все еще сообщается, можно выполнить последующий журнал.

    Из этого примера мы можем видеть, чтоКак только в предыдущем (синхронном) коде возникает исключение, которое не перехвачено разработчиком, следующий код выполняться не будет.. Итак, если вы хотите, чтобы следующий код нормально работал в текущем блоке кода, который может дать сбой, вам нужно использовать try...catch для активного перехвата исключений.

    На самом деле стоит изучить вопрос о том, как ошибочный код мешает выполнению последующего кода. Далее подробное обсуждение. Поскольку на рынке существует много браузеров, реализация стандарта непоследовательна. Итак, вывод здесь основан только на Chromev81.0.4044.92. В ходе обсуждения мы задействовали две группы понятий: синхронный код и асинхронный код, период написания кода и период выполнения кода.

    • Сценарий 1. Случай «код синхронизации + код синхронизации».

    Видно, что код синхронизации после кода синхронизации ошибки не выполняется.

    • Сценарий 2: «Синхронный код + асинхронный код»

    В случае вышеизложенного асинхронный код также затрагивается и не выполняется.

    • Сценарий 3: «Асинхронный код + синхронный код»:

    Видно, что ошибка в асинхронном коде не повлияет на выполнение последующего синхронного кода.

    • Сцена 4: «Асинхронный код + асинхронный код»:

    Ошибки в асинхронном коде также не влияют на выполнение последующего асинхронного кода.


    Мы сосредоточимся на Сценарии 2 и Сценарии 3, если мы понимаем причину результатов двух как: «Это потому, что синхронный код всегда выполняется перед асинхронным кодом во время выполнения кода. Если синхронный код, выполняемый первым, не будет ошибки, то следующий код будет выполняться нормально, иначе следующий код не будет выполняться", тогда давайте посмотрим на этот пример:

    Само собой разумеется, что во время выполнения js-движок сначала выполнит наш код синхронизации «console.log(a);». В этот момент наша синхронная генерация пошла не так, по нашему предварительному заключению даже асинхронный код после потока выполнения не должен выполняться. На самом деле они оба казнят, верно? Давайте посмотрим на другой пример:

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

    Следовательно, на данный момент мы можем в основном сделать следующий вывод: во время выполнения в двух кодах друг за другом возникает проблема того, как код на неправильной стороне влияет на продолжение выполнения кода на другой стороне. не имеет ничего общего с асинхронным кодом, только имеет какое-то отношение к синхронному коду, не имеет ничего общего с периодом выполнения кода, только с периодом написания кода.Другими словами, сбой асинхронного кода или нет не повлияет на продолжение выполнения другого кода. И неисправный синхронный код, если он будет написан раньше другого кода в период написания кода, и мы не отловим его вручную, то он повлияет на другой код (будь то синхронный или асинхронный код).

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

  2. грамматика

    try {
       try_statements
    }
    [catch (exception_var_1 if condition_1) { // non-standard
       catch_statements_1
    }]
    ...
    [catch (exception_var_2) {
       catch_statements_2
    }]
    [finally {
       finally_statements
    }]
    

    То есть существуют следующие комбинации:

    • try...catch
    • try...finally
    • try...catch...finally

    Среди них несколько уловов могут быть соединены последовательно.Этот синтаксис называется «Условные блоки улавливания», потому что это не стандартная спецификация, и многие браузеры больше не поддерживают его, поэтому здесь он не будет подробно обсуждаться.

    При использовании try...catch следует учитывать следующие моменты в синтаксисе:

    • Предложение catch получает объект ошибки. Даже если вы не хотите использовать объект ошибки, вы должны дать ему имя, иначе браузер выдаст ошибку «Uncaught SyntaxError».

    • Предложение finally выполняется в любом случае и имеет приоритет над оператором return. Например, следующая функция вернет 0, а не 2:

      function testFinally(){
          try{
              return 2;
          }catch(e){
              return 1;
          }finally{
              return 0;
          }
      }
      
    • try...catch могут быть вложены друг в друга. Выброшенные в нем ошибки будут удалены из этой ошибкинедавнийБлок catch ловит. Это «недавнее» включает в себя два случая. Первый заключается в том, что в одном и том же слое один и тот же слой спускается к ближайшему блоку catch (поскольку в браузере поддерживается только firefox в сериях 1-59, такого рода ситуации, связанные с условными блоками catch, можно игнорировать); Второй — это, на разных уровнях, блок catch на ближайшем к нему уровне.

      try {
        try {
          throw new Error("oops");
        }
        catch (ex) {
          console.error("inner", ex.message);
          throw ex;
        }
        finally {
          console.log("finally");
        }
      }
      catch (ex) {
        console.error("outer", ex.message);
      }
      
      // Output:
      // "inner" "oops"
      // "finally"
      // "outer" "oops"
      
  3. Диапазон обработки

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

Напоминание: синтаксическая ошибка обнаружена на этапе компиляции движка js, в это время ваш код js не был выполнен. Таким образом, синтаксические ошибки не могут быть обнаружены вашим js-кодом.

Не могу поймать синтаксическую ошибку:

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

Однако есть особый случай async...await. Ошибки в асинхронном коде, принимаемые ожиданием, могут быть перехвачены с помощью try...catch:

О попробуй... поймай,эта статьяС относительно новой точки зрения он делает вывод об исключениях, которые он может поймать:

Исключение, которое может быть перехвачено с помощью try-catch, должно быть перехвачено только тогда, когда выполнение потока вошло в блок кода try-catch и находится в try-catch, когда сообщается об ошибке.

Является ли это утверждение правильным и достаточно строгим, еще предстоит продемонстрировать и проверить.

window.onerror
  1. мотивация

    Как мы все знаем, многие библиотеки классов для мониторинга ошибок и отчетов реализованы на основе этой функции, и мы ожидаем, что она будет обрабатывать те ошибки, с которыми не может справиться try...catch.

  2. грамматика

window.onerror = function(message, source, lineno, colno, error) { ... }

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

  • message: строка сообщения об ошибке (string)
  • источник: URL файла ошибки js (строка)
  • lineno: номер строки кода ошибки (номер)
  • colno: номер столбца кода ошибки (число)
  • error: Объект ошибки(объект)

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

Здесь стоит упомянуть, что если вы хотите, чтобы window.onerror перехватывал ошибку и отменял поведение по умолчанию (это поведение по умолчанию заключается в выводе неперехваченной ошибки xxxxError в консоли браузера),Обработчик события должен вернуть true. Это противоречит здравому смыслу. Потому что в системе событий пользовательского интерфейса отмена поведения по умолчанию обычно выполняется путем возврата false.

  1. Диапазон обработки

Теперь наступает та часть, которая нас больше всего беспокоит. То есть какие ошибки может обрабатывать window.onerror?

Вот что говорит MDN:

When a JavaScript runtime error (including syntax errors and exceptions thrown within handlers) occurs, an error event using interface ErrorEvent is fired at window and window.onerror() is invoked

Это предложение говорит так, как будто все ошибки javascript во время выполнения могут быть обнаружены с помощью window.onerror. На самом деле после проверки на chromevxxx и firefoxv75.0 это предложение неверно. window.onerror не панацея.

  1. window.onerror не может поймать синтаксическую ошибку:

    window.onerror = function(message, source, lineno, colno, error) {
        console.log('通过window.onerror捕获到的异常:',message);
        return true;
    }
    
    const a = 1'; // 语法错误
    
    

    Видно, что синтаксическая ошибка не фиксируется и все равно отображается в консоли браузера.

  2. window.onerror не может отлавливать ошибки из асинхронного кода

    Но стоит отметить, что window.onerror может ловить ошибки в setTimeout и setInterval, которые также являются асинхронным кодом.:

     window.onerror = function(message, source, lineno, colno, error) {
        console.log('通过window.onerror捕获到的异常:',message);
        return true;
    }
    
    setTimeout(()=> {
        console.log(a);
    },0)
    
    setInterval(() => {
         console.log(b);
     }, 1000);
    
    

  3. window.onerror не может обнаруживать сбои загрузки статических ресурсов.

  4. Как сказано в MDN, window.onerror можно перехватить.происходит в обработчике событияИсключения, включая синтаксические ошибки.

     ```
     <script>
     window.onerror = function(message, source, lineno, colno, error) {
         console.log('通过window.onerror捕获到的异常:',message);
         return true;
     }
     </script>
     
     <img id="img" src="./fake.png" onerror="console.log(abc)">
     ```
    

    Консоль выведет:

    Исключение, перехваченное window.onerror: Uncaught ReferenceError: abc не определен

    Похоже, window.onerror не панацея. Мы также ожидаем, что он полностью поймает нижнюю часть try...catch, и, как оказалось, у него также есть ошибки, которые он не может отловить.

window.addEventListener('error',()=>{}) и element.onerror
  1. мотивация

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

  2. грамматика

    Как говорит MDN:

    When a resource (such as an <img> or<script>) fails to load, an error event using interface Event is fired at the element that initiated the load, and the onerror() handler on the element is invoked. These error events do not bubble up to window, but (at least in Firefox) can be handled with a window.addEventListener configured with useCapture set to True.

    Видно, что они находятся на одном и том же пути распространения события: одно — целевая фаза, а другое — фаза захвата. С помощью проверки Firefox и Chrome могут перехватить этот тип ошибки, когда сетевой ресурс не загружается, установив для второго параметра window.addEventListener() значение true.

  3. Диапазон обработки

    Мы провели пример, чтобы увидеть, может ли Window.addeventListener («ошибка») действительно соответствовать нашим ожиданиям:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>网络资源加载错误捕获</title>
        <script>
            window.addEventListener('error', (error) => {
                console.log('使用window.addEventListener能捕获到异常:', error);
            }, true)
        </script>
        <script src="./fake.js"></script>
        <link rel="stylesheet" href="./fake.css">
    </head>
    <body>
        <img id="img" src="./fake.png">
        <video id="video" src="fake.mp4"></video>
        <audio src="fake.mp3"></audio>
        <iframe id="iframe" src="./test4.html" frameborder="0" ></iframe>
    </body>
    </html>
    

    При этом все сетевые ресурсы не существуют, и само собой разумеется, что будет сообщено об ошибке. Давайте посмотрим на результаты печати консоли:

    Как видите, все ошибки загрузки ресурсов, кроме iframe, перехватываются методом window.addEventListener('error').

    Кажется, что к iframe следует относиться по-другому.

Promise Catch и window.addEventListener("unhandledrejection",()=> {})
  1. мотивация

    В настоящее время в асинхронном коде, за исключением того, что не упоминается захват ошибок кода обещания, setTimeout/setInterval может быть перехвачен с помощью window.onerror, а async...await может быть перехвачен с помощью try...catch. Далее давайте посмотрим, как следует отлавливать ошибки в коде промисов.

  2. грамматика

    const promiseObj = new Promise(executor);
    
    promiseObj
    .then(handleFulfilledA,handleRejectedA)
    .then(handleFulfilledB,handleRejectedB)
    .then(handleFulfilledC,handleRejectedC);
    
    // 或者
    promiseObj
    .then(handleFulfilledA)
    .then(handleFulfilledB)
    .then(handleFulfilledC)
    .catch(handleRejectedAny);
    

    MDN предпочитает последний способ написания. Синтаксический стиль этого письма ближе к try...catch, который легче принять.

  3. Диапазон обработки

    В настоящее время не существует технических средств для перехвата исключений, генерируемых промисами, а ловушка, связанная с промисами, приходит как раз в нужное время. Давайте проверим это:

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

    window.addEventListener("unhandledrejection", function(e){
        console.log('捕获到的promise异常:', e);
        e.preventDefault();
    });
    new Promise((res)=>{console.log(a)});
    

    Здесь стоит отметить, что в отличие от DOM1 сreturn true/false, используется механизм событий уровня DOM3event.preventDefault()Приходи и уходи, чтобы отменить поведение события по умолчанию. Опять же, поведение по умолчанию здесь — «распечатать неперехваченную ошибку xxxxError в консоли браузера».

iframe и iframe.onload

Как упоминалось в разделе window.addEventListener('error') выше, в дополнение к iframe, он может перехватывать другие типы ошибок загрузки сетевых статических ресурсов. Остается iframe, что нам делать?

В Интернете говорится, что использовать атрибут onerror так же просто, как прослушивать событие ошибки iframe, например следующее:

window.frames[0].onerror = function (message, source, lineno, colno, error) {
    console.log('捕获到 iframe 异常:',{message, source, lineno, colno, error});
    return true;
};

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

The specification requires that all HTML elements support on onerror event. However, it does NOT require that all elements supporting network fetches raise fire a simple event called onerror. That is, elements must support allowing applications to set error handlers, but there is no (generic) requirement that the event be raised, in either HTML or the Fetch specification.

For now, this is WontFix, for Working as Intended (and as Specified)

Подразумевается, что нормы просто требуют, чтобы мы реализуют поддержку всех слушателей событий Elemer Element Element Element, но не требуют от нас, чтобы вызвать такое событие. Особенно на элементе Iframe, мы считаем, что существует сетевое исследование и другие риски безопасности.

Окончательный вывод:WontFix. В итоге мы вынуждены прибегнуть к некоторым хакам:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
 </head>
    <iframe 
        id="iframe" 
        src="./test22.html" 
        frameborder="0" 
        onerror="console.log(event)"
        onload="console.log(this.contentWindow.document.title)"
    >
    </iframe>
</body>
</html>

Давайте посмотрим на печать консоли каждого браузера:

chromev81.0.4044.92:

firefoxv75.0:

Microsoft Edge v44.18362.329.0:

Как видите, обработчик события onerror iframe не выполняется. Кроме того, после сбоя загрузки заголовок документа страницы, загруженной iframe, будет установлен на «Ошибка». Наконец, решение, которое мы реализуем в сочетании с window.onerror, выглядит следующим образом:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
 </head>
    <iframe 
        id="iframe" 
        src="./test22.html" 
        frameborder="0" 
    >
    </iframe>
    <script>
        window.onerror = function(message, source, lineno, colno, error) { 
            console.log('通过window.onerror捕获到的异常:',message);
            return true;
        }

        const iframe = document.getElementById('iframe');
        iframe.onload = function(event){
            const title = this.contentWindow.document.title.toLowerCase();
            if(title === 'error'){
                throw new Error('iframe加载失败');
            }
        }
    </script>
</body>
</html>

В результате консоль выводит:

Исключение, перехваченное через window.onerror: Uncaught Error: iframe не удалось загрузить

разное
  1. page crash

Если вы хотите справиться с ситуацией сбоя страницы, вы можете обратиться к этомуСообщение блога. Решение, которое он дает, таково:

 if(sessionStorage.getItem('good_exit') &&
      sessionStorage.getItem('good_exit') !== 'true') {
      /*
         insert crash logging code here
     */
      alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));
  }
  
window.addEventListener('load', function () {
      sessionStorage.setItem('good_exit', 'pending');
      setInterval(function () {
         sessionStorage.setItem('time_before_crash', new Date().toString());
      }, 1000);
   });

   window.addEventListener('beforeunload', function () {
      sessionStorage.setItem('good_exit', 'true');
   });

  

Очевидно, что эта схема обработки такая же, как и у iframe. Мы просто знаем, что страница падает, а конкретная причина краха или объект errorEvent, который мы ожидаем, не существует.

Это решение в основном опирается на разворачивающийся факт. То естьСбой страницы не может вызвать событие beforeunload. Следовательно, после сбоя страницы значение good_exit, записанное в sessionStorage, может быть только «ожидающим». Затем в следующий раз, когда страница появится, условие if будет истинным, и мы сможем что-то сделать с последним сбоем страницы. Например, пользователю выдается дружественная подсказка, или информация о последнем сбое отправляется в систему мониторинга ошибок внешнего интерфейса и т. д.

Есть еще один способ сделать это с помощью сервис-воркеров, подробнее см.Как отслеживать сбои веб-страницы?Эта статья.

  1. script error

Кажется, не существует универсального решения, если вы хотите справиться с этим типом ошибки скрипта. Когда мы сталкиваемся с такими ошибками в разработке, у нас обычно есть два варианта: 1) Избежать ограничений политики одного и того же происхождения и удалить такие ошибки из источника; 2) Когда сторонние домены находятся под нашим контролем, Вы можете попробовать использовать взлом для сбора информации об определенных сторонних исключениях js.

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

Во-первых, это КОРС. То есть добавить тег

Во-вторых, использовать веб-прокси-сервер. Это нормальная практика работы с междоменными доменами.

Когда сторонний домен находится под нашим контролем и мы не хотим использовать первый метод обработки, мы можем использовать взлом для решения ошибки скрипта под определенной функцией.Подробности см.Альтернативный способ решения «Ошибки скрипта». Стоит отметить, что в ответ на вывод этой статьи правильные комментарии пользователей сети:

То есть решение, упомянутое в этой статье, решает только частный случай ошибки скрипта — ошибку в обработчике события. на основеБраузеры не будут выполнять междоменный перехват исключений, вызванных try-catch.этот факт , взломав нативный метод addEventListener для перехвата ошибок в функции обработчика событий [зарегистрированной через addEventListener] в стороннем скрипте домена, в этом и заключается принцип этой схемы. Однако эта схема не может отлавливать ошибки вне других сценариев. Так что это не универсальное решение. В настоящее время я не видел общего решения, которое перехватывало бы все ошибки сценария, не нарушая ограничений политики одного и того же источника.

Второй шаг: в соответствии с фактическими потребностями и соответствующей обработкой;

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

Затем второй шаг — поговорить о том, как бороться с этими сообщениями об ошибках. Сюда входят следующие два способа:

  • Сообщите информацию об ошибке в систему внешнего мониторинга.
  • Через обратную связь интерфейса сообщите пользователю, что на текущей странице есть ошибка.
Сообщите информацию об ошибке в систему внешнего мониторинга

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

Нефатальные ошибки, которые можно определить на основе одного или нескольких из следующих условий:

  • Не влияет на основную задачу пользователя
  • Влияет только на часть страницы
  • можно восстановить
  • Повторение действий может устранить ошибки

Неустранимая ошибка, которая может быть определена одним или несколькими из следующих условий:

  • Приложение вообще не может продолжать работать;
  • Ошибка явно влияет на основную операцию пользователя;
  • приведет к другим сопутствующим ошибкам.

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

Вообще говоря, ошибки, которые активно не перехватываются с помощью try...catch, но в конечном итоге перехватываются с помощью window.onerror, скорее всего, приведут к тому, что приложение не сможет продолжить работу или повлияет на основные операции пользователя. Ошибки этого типа должны быть исследованы и исправлены в первую очередь.

В некоторых случаях, если страницу необходимо обновить, чтобы приложение продолжало работать должным образом, пользователь должен быть уведомлен об этом, и ему должна быть предоставлена ​​кнопка для обновления страницы.

После определения уровня ошибки, а также полученной информации об ошибке мы можем сообщить об этом в систему мониторинга.

Будь то отчет о скрытой точке или отчет об ошибке, общим решением является использование объекта изображения для отправки запроса. Это дает следующие преимущества:

  • Совместимость с браузерами хорошая. Объекты изображений поддерживаются всеми браузерами.
  • Ограничения политики одного и того же происхождения можно избежать. В реальной разработке обычно один сервер отвечает за получение отчетов об ошибках со всех серверов. В этом случае возникнет междоменная проблема, и простой XMLHttpRequest эту проблему не решит.
  • В процессе отчетности вероятность самой ошибки относительно низка. Если вы инкапсулируете библиотеку ajax самостоятельно или используете внешнюю библиотеку ajax, если есть проблемы с этими библиотеками, и вы все еще ожидаете, что они сообщат, вполне возможно, что результат не будет успешно сообщен.

Ниже мы просто реализуем метод для сообщения об ошибках:

function postError(type, msg) {
    const img = new Image();
    img.src = `log.php?type=${encodeURICoponent(type)}&msg=${encodeURICoponent(msg)}`
}
Через обратную связь интерфейса сообщить пользователю, что на текущей странице есть ошибка

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

Язык подсказок должен быть дружелюбным и понятным пользователям. Второй относительно точен. Проявления подсказок обычно включают легкие всплывающие подсказки, всплывающие окна с подсказками, частичную замену страницы и замену всей страницы (аналогично обычным сообщениям об ошибке 404). Какую форму выбрать, можно определить в зависимости от конкретного бизнес-сценария и уровня ошибки.

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

Суммировать

Подытожьте основное содержание этой статьи картинкой:

использованная литература