предисловие
В качестве фронтенда, даже если процесс разработки будет очень тщательным и будет достаточно самотестирования, при сложных операциях разных пользователей неизбежно возникнут непредвиденные проблемы для программистов, которые принесут огромные убытки компании или частному лицу. В настоящее время необходима система мониторинга ошибок переднего плана, которая может вовремя сообщать об ошибках и помогать программистам хорошо устранять ошибки. Далее поговорим о распространенных ошибках и о том, как с ними бороться.
В этой статье в основном обсуждаются следующие моменты:
- Распространенные типы ошибок JS
- Общие методы обработки ошибок JS
- Как сообщить и некоторые мысли о том, что сообщить
вопрос:
- Как я могу узнать в режиме реального времени, что ресурсы, такие как JS, CSS, img и другие ресурсы, не загружаются (CDN или кровать изображения зависла, удалена непреднамеренно или имя файла изменилось)? вместо того, чтобы пользователи говорили вам?
- Как сообщить полезную информацию об ошибке, чтобы программисты могли быстро найти ошибку и исправить ее? Вместо того, чтобы сообщать какую-то запутанную информацию?
- Как эффективно использовать файлы SourceMap и работать с сообщениями об ошибках в современных разработках, не требующих сжатия и искажения кода?
- Как что-то может пойти не так, не попросив пользователя помочь вам воспроизвести это? Хотите модель? Хотите сделать шаги?
- Как лучше посчитать распределение проблем (модели устройств, браузеры, география, пропускная способность и т. д.), и самостоятельно выбрать тенденцию совместимости по данным?
- ...
Распространенные ошибки
- ошибка сценария
- Грамматические ошибки
- ошибка выполнения
- ошибка синхронизации
- Асинхронная ошибка
- Ошибка обещания
- Ошибка сети
- ошибка загрузки ресурса
- ошибка пользовательского запроса
Вы можете прочитать исходный код библиотеки классов мониторингаerrorWatchДля углубления понимания его также можно использовать непосредственно в проекте.
Грамматические ошибки
Например, английские иероглифы записываются как китайские иероглифы. Обычно легко найти во время разработки.
![syntaxError](https://s3.timeweb.com/newworld58-e1e8f297-7d39-4eff-9f5d-42281e40a914/UnderSkyWeb-577b56c7b48aadd09f416bd2067f5b9ba2af5afca11bdcd99bd37cf1741001e516ef05a3.jpg)
Синтаксические ошибки не могут бытьtry
catch
иметь дело с
try {
const error = 'error'; // 圆角分号
} catch(e) {
console.log('我感知不到错误');
}
ошибка синхронизации
Когда JS-движок выполняет скрипт, он помещает задачи в стек событий блоками и опрашивает их на выполнение.Каждая задача события имеет свой собственный контекст.
Ошибки в коде, выполняющемся синхронно в текущем контексте, могут бытьtry
catch
Захват, чтобы гарантировать выполнение последующего синхронного кода.
try {
error
} catch(e) {
console.log(e);
}
Асинхронная ошибка
ОбщийsetTimeout
и другие методы создадут новую событийную задачу и вставят ее в стек событий для последующего выполнения.
такtry
catch
Ошибки кода из других контекстов не могут быть обнаружены.
try {
setTimeout(() => {
error // 异步错误
})
} catch(e) {
console.log('我感知不到错误');
}
Чтобы облегчить анализ возникающих ошибок, обычно используютwindow.onerror
событие для прослушивания возникновения ошибки.
это лучше, чемtry
catch
Возможность захвата информации об ошибках сильнее.
/**
* @param {String} msg 错误描述
* @param {String} url 报错文件
* @param {Number} row 行号
* @param {Number} col 列号
* @param {Object} error 错误Error对象
*/
window.onerror = function (msg, url, row, col, error) {
console.log('我知道错误了');
// return true; // 返回 true 的时候,异常不会向上抛出,控制台不会输出错误
};
![windowOnerror](https://s3.timeweb.com/newworld58-e1e8f297-7d39-4eff-9f5d-42281e40a914/UnderSkyWeb-f75d1d355be8653776803616ffb0c7091c4a0349830a073e942dafe9cbe802349e536c6f.jpg)
- window.onerror Примечания
-
window.onerror
Может ловить общий синтаксис, синхронные, асинхронные ошибки и другие ошибки; -
window.onerror
не могу пойматьPromise
ошибка, сетевая ошибка; -
window.onerror
Должен выполняться перед всеми JS-скриптами, чтобы не пропустить; -
window.onerror
Его легко переопределить, его следует учитывать при обработке обратных вызовов, и люди также используют этот прослушиватель событий.
Ошибка сети
Поскольку исключение сетевого запроса не будет всплывать, его следует получить на этапе захвата событий.
мы можем использоватьwindow.addEventListener
. Например, код, изображения и т. д.CDN
Когда ресурсов не хватает, крайне важно своевременно получать обратную связь.
window.addEventListener('error', (error) => {
console.log('404 错误');
console.log(error);
// return true; // 中断事件传播
}, true);
![addEventListener](https://s3.timeweb.com/newworld58-e1e8f297-7d39-4eff-9f5d-42281e40a914/UnderSkyWeb-da14a04e23c998e6674c4808307fe12f50dd215921966ebd29858205817013d6c44123ae.jpg)
Для таких ошибок загрузки ресурсов можно получить достаточно информации в объекте события, и разработчик может быть уведомлен в кратчайшие сроки с помощью SMS, DingTalk и т. д.
window.addEventListener('error', (e) => {
if (e.target !== window) { // 避免重复上报
console.log({
url: window.location.href, // 引用资源地址
srcUrl: e.target.src, // 资源加载出错地址
})
}
}, true);
-
window.onerror
а такжеwindow.addEventListener
window.addEventListener
Преимущество в том, что вы не боитесь перезаписи обратных вызовов, вы можете прослушивать несколько функций обратного вызова, но не забывайте их уничтожать, чтобы избежать утечек памяти и ошибок.
но не могу получитьwindow.onerror
Такое богатство информации. Обычно используется толькоwindow.addEventListener
для мониторинга ошибок загрузки ресурсов.
- Для пользовательских ошибок сетевых запросов лучше всего сообщать о них вручную.
Ошибка обещания
если вы используетеpromise
еще нетcatch
, тогдаonerror
Тоже бессильный.
Promise.reject('promise error');
new Promise((resolve, reject) => {
reject('promise error');
});
new Promise((resolve) => {
resolve();
}).then(() => {
throw 'promise error';
});
Также вы можете использоватьwindow.onunhandledrejection
илиwindow.addEventListener("unhandledrejection")
для контроля ошибок.
Получите объект PromiseError, который может разрешить объект ошибки вreason
свойства, что-то вродеstack
.
Конкретную совместимую обработку можно увидеть в TraceKit.js.
Метод отчетности
-
img
отчет -
ajax
отчет
function report(errInfo) {
new Image().src = 'http://your-api-website?data=' + errInfo;
}
ajax
Следует использовать только библиотеку классов, которая аналогична.
- Уведомление:
img
Запрос имеет ограничение по длине, данные слишком велики, лучше использоватьajax.post
.
Пополнить
Script error
Скрипты, ссылающиеся на разные доменные имена, если нет специальной обработки, сообщает об ошибке. Как правило, браузер из соображений безопасности и не отображает конкретные ошибки, ноScript error
.
Например, у других есть скрытые мотивы цитировать ваш бизнес-код с закрытым исходным кодом в Интернете, и, конечно же, вы не хотите, чтобы они знали сообщение об ошибке вашего скрипта.
Как решить проблему междоменных отчетов об ошибках собственного скрипта?
- Все ресурсы переключаются на единое доменное имя, но это теряется
CDN
Преимущества. - в файле скрипта
HTTP response header
установить вCORS
.
-
Access-Control-Allow-Origin: You-allow-origin
; - добавить тег скрипта
crossorigin
свойства, такие как<script src="http://www.xxx.com/index.js" crossorigin></script>
заголовки ответов иcrossorigin
Проблема ценности
-
crossorigin="anonymous"
(По умолчанию),CORS
не равноYou-allow-origin
, не могу принестиcookie
-
crossorigin="use-credentials"
а такжеAccess-Control-Allow-Credentials: true
,CORS
не может быть установлено на*
,Группаcookie
. еслиCORS
не равноYou-allow-origin
, браузер не загружает файл js.
Когда вы хорошо справляетесь с имеющимися в вашем распоряжении ресурсамиcors
час,Script error
В принципе, это можно отфильтровать и не сообщать.
Сказав все это, есть одна очень важная тема, как я могу анализировать сообщения об ошибках, которые я могу поймать?
Анатомия ошибок JavaScript
ОдинJavaScript
Ошибки обычно состоят из следующих ошибок
- сообщение об ошибке
- трассировки стека
![error](https://s3.timeweb.com/newworld58-e1e8f297-7d39-4eff-9f5d-42281e40a914/UnderSkyWeb-ac547d279b978c053da34d4230018e03f1efe7e64450fbb6689463e1c3b842aaecb5eb56.jpg)
![consoleError](https://s3.timeweb.com/newworld58-e1e8f297-7d39-4eff-9f5d-42281e40a914/UnderSkyWeb-d2866e397d75a091d668608f46cca785a6d364419376cd8ecad7b873041e3e94796e3015.jpg)
Разработчики могут выдать ошибку JavaScript по-разному:
- throw new Error('Problem description.')
- throw Error('Problem description.') <-- equivalent to the first one
- throw 'Problem description.' <-- bad
- throw null <-- even worse
Рекомендуется использовать второй, третий и четвертый браузеры, которые не могут создавать стеки трассировки для двух вышеуказанных методов.
Если сообщение об ошибке в стеке трассировки может быть проанализировано для каждой строки, эта строка будет указана в соответствующем списке.SourceMap
Не можете ли вы найти конкретный исходный код каждой строки?
Проблема в том, что разные браузеры не имеют единого стандартного формата вышеуказанной информации. Сложность заключается в решении вопросов совместимости.
Напримерwindow.onerror
Объект ошибки пятого параметра добавлен в 2013 г.WHATWG
в спецификации.
Ранний Safari и IE10 еще не сделали этого, Firefox добавил объект Error из версии 14, а chrome был добавлен только в 2013 году.
Рекомендуемая практика
-
window.onerror
Это лучший способ отлавливать ошибки JS и сообщать только при наличии допустимого объекта Error и стека трассировки. Также можно избежать ошибок, на которые нельзя повлиять, таких как ошибки плагинов и междоменные ошибки с неполной информацией. -
try catch
Улучшено, информация об ошибках стала более полной, что можно компенсировать.window.onerror
недостаточности. Но, как было сказано ранее,try catch
Невозможно поймать асинхронные ошибки иpromise
ошибка, ни использоватьV8
Оптимизация работы двигателя.
Например, компания Тенсент.BadJS, дал следующие рекомендацииtry catch
пакет
- setTimeout и setInterval
- привязка события
- ajax callback
- определить и потребовать
- Главный деловой вход
Нужно ли делать такой мелкозернистый пакет, зависит от ситуации.
SourceMap
Например, следующий стек трассировки ошибок (трассировка стека)
ReferenceError: thisIsAbug is not defined
at Object.makeError (http://localhost:7001/public/js/traceKit.min.js:1:9435)
at http://localhost:7001/public/demo.html:28:12
можно разобрать в следующий формат
[
{
"args" : [],
"url" : "http://localhost:7001/public/js/traceKit.min.js",
"func" : "Object.makeError",
"line" : 1,
"column" : 9435,
"context" : null
},
{
"args" : [],
"url" : "http://localhost:7001/public/demo.html",
"func" : "?",
"line" : 28,
"column" : 12,
"context" : null
}
]
Имея звания и соответствующиеSourceMap
Файл может быть проанализирован для получения информации об исходном коде.
![sourceMapDel](https://s3.timeweb.com/newworld58-e1e8f297-7d39-4eff-9f5d-42281e40a914/UnderSkyWeb-db728e4003715ab33462378db3324ff627bb7c7ee389d61f155314da8fadd7ef759f5003.jpg)
Разобрать результаты
![sourceMapDel](https://s3.timeweb.com/newworld58-e1e8f297-7d39-4eff-9f5d-42281e40a914/UnderSkyWeb-36a8bfb7ebd17579f709583b9ee1fedbb5149a2d25f49cfc7927b48484e6efc2796dc936.jpg)
Код обработки следующий:
import { SourceMapConsumer } from 'source-map';
// 必须初始化
SourceMapConsumer.initialize({
'lib/mappings.wasm': 'https://unpkg.com/source-map@0.7.3/lib/mappings.wasm',
});
/**
* 根据sourceMap文件解析源代码
* @param {String} rawSourceMap sourceMap文件
* @param {Number} line 压缩代码报错行
* @param {Number} column 压缩代码报错列
* @param {Number} offset 设置返回临近行数
* @returns {Promise<{context: string, originLine: number | null, source: string | null}>}
* context:源码错误行和上下附近的 offset 行,originLine:源码报错行,source:源码文件名
*/
export const sourceMapDeal = async (rawSourceMap, line, column, offset) => {
// 通过sourceMap库转换为sourceMapConsumer对象
const consumer = await new SourceMapConsumer(rawSourceMap);
// 传入要查找的行列数,查找到压缩前的源文件及行列数
const sm = consumer.originalPositionFor({
line, // 压缩后的行数
column, // 压缩后的列数
});
// 压缩前的所有源文件列表
const { sources } = consumer;
// 根据查到的source,到源文件列表中查找索引位置
const smIndex = sources.indexOf(sm.source);
// 到源码列表中查到源代码
const smContent = consumer.sourcesContent[smIndex];
// 将源代码串按"行结束标记"拆分为数组形式
const rawLines = smContent.split(/\r?\n/g);
let begin = sm.line - offset;
const end = sm.line + offset + 1;
begin = begin < 0 ? 0 : begin;
const context = rawLines.slice(begin, end).join('\n');
// 记得销毁
consumer.destroy();
return {
context,
originLine: sm.line + 1, // line 是从 0 开始数,所以 +1
source: sm.source,
}
};
каждый согласноSourceMap
Формат файла, вы можете понять этот код хорошо.