Этот README содержит информацию, которую я узнал за многие годы о том, как справляться с ошибками JavaScript, сообщать о них на сервер и преодолевать множество ошибок, которые могут усложнить все это. Браузеры улучшились в этой области, но еще есть куда совершенствоваться, чтобы все приложения могли разумно и надежно обрабатывать любые возникающие ошибки.
Тестовые примеры для содержимого, найденного в этом руководстве, можно найти по адресу https://mknichel.github.io/javascript-errors/.
Оглавление
Введение
Анатомия ошибки JavaScript
Создание ошибки JavaScript
Сообщения об ошибках
Формат трассировки стека
Обнаружение ошибок JavaScript
окно.onerror
попробовать/поймать
Защищенные точки входа
Обещания
Веб-работники
Расширения Chrome
Обнаружение, отчетность и исправление ошибок — важная часть любого приложения, обеспечивающая работоспособность и стабильность приложения. Поскольку код JavaScript также выполняется на клиенте и во многих различных средах браузера, оставаться в курсе ошибок JS в вашем приложении также может быть сложно. Не существует формальных веб-спецификаций о том, как сообщать об ошибках JS, которые вызывают различия в реализации каждого браузера. Кроме того, в браузерах было много ошибок, связанных с ошибками JavaScript, что еще больше усложняло задачу. На этой странице рассматриваются эти аспекты ошибок JS, чтобы будущие разработчики могли лучше обрабатывать ошибки, а браузеры, мы надеемся, сойдутся на стандартизированных решениях.
Ошибка JavaScript состоит из двух основных частей: сообщения об ошибке и трассировки стека . Сообщение об ошибке представляет собой строку, описывающую, что пошло не так, а трассировка стека описывает, где в коде произошла ошибка. Ошибки JS могут быть вызваны либо самим браузером, либо кодом приложения.
Ошибка JS может быть выдана браузером, когда часть кода выполняется неправильно, или она может быть выдана непосредственно кодом.
Например:
вар а = 3;а();
В этом примере переменная, которая на самом деле является числом, не может быть вызвана как функция. Браузер выдаст ошибку типа TypeError: a is not a function
с трассировкой стека, указывающей на эту строку кода.
Разработчик также может захотеть выдать ошибку в фрагменте кода, если не выполняется определенное предварительное условие. Например
если (!checkPrecondition()) { throw new Error("Не соответствует предварительному условию!");}
В этом случае ошибка будет Error: Doesn't meet precondition!
. Эта ошибка также будет содержать трассировку стека, указывающую на соответствующую строку. Ошибки, возникающие в коде браузера и приложения, можно обрабатывать одинаково.
Существует несколько способов, которыми разработчики могут выдать ошибку в JavaScript:
throw new Error('Problem description.')
throw Error('Problem description.')
<- эквивалентно первому
throw 'Problem description.'
<-- плохо
throw null
<-- еще хуже
Выдавать строку или значение NULL на самом деле не рекомендуется, поскольку браузер не будет прикреплять трассировку стека к этой ошибке, теряя контекст того, где эта ошибка произошла в коде. Лучше всего создать реальный объект Error, который будет содержать сообщение об ошибке, а также трассировку стека, указывающую на правильные строки кода, в которых произошла ошибка.
Каждый браузер имеет свой собственный набор сообщений, которые он использует для встроенных исключений, например, как в приведенном выше примере при попытке вызвать нефункцию. Браузеры будут пытаться использовать одни и те же сообщения, но поскольку спецификации нет, это не гарантируется. Например, и Chrome, и Firefox используют {0} is not a function
для приведенного выше примера, в то время как IE11 сообщит об Function expected
(в частности, без сообщения о том, какую переменную пытались вызвать).
Однако браузеры также часто расходятся. Если в операторе switch
имеется несколько операторов по умолчанию, Chrome выдаст "More than one default clause in switch statement"
а Firefox сообщит "more than one switch default"
. По мере добавления новых функций в Интернет эти сообщения об ошибках необходимо обновлять. Эти различия могут проявиться позже, когда вы попытаетесь обработать сообщения об ошибках из запутанного кода.
Вы можете найти шаблоны, которые браузеры используют для сообщений об ошибках, по адресу:
Firefox – http://mxr.mozilla.org/mozilla1.9.1/source/js/src/js.msg
Chrome – https://code.google.com/p/v8/source/browse/branches/bleeding_edge/src/messages.js
Internet Explorer — https://github.com/Microsoft/ChakraCore/blob/4e4d4f00f11b2ded23d1885e85fc26fcc96555da/lib/Parser/rterrors.h
В некоторых исключениях браузеры выдают разные сообщения об ошибках.
Трассировка стека — это описание того, где в коде произошла ошибка. Он состоит из серии кадров, каждый из которых описывает определенную строку кода. Самый верхний кадр — это место, где возникла ошибка, а последующие кадры — это стек вызовов функций или то, как был выполнен код, чтобы добраться до той точки, где возникла ошибка. Поскольку JavaScript обычно объединяется и минимизируется, также важно иметь номера столбцов, чтобы можно было найти точный оператор, когда в данной строке имеется множество операторов.
Базовая трассировка стека в Chrome выглядит так:
at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9) at http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3
Каждый кадр стека состоит из имени функции (если применимо и код не был выполнен в глобальной области видимости), сценария, из которого она была получена, а также номера строки и столбца кода.
К сожалению, не существует стандарта для формата трассировки стека, поэтому он зависит от браузера.
Трассировка стека Microsoft Edge и IE 11 похожа на трассировку Chrome, за исключением того, что в ней явно указан глобальный код:
at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:3) at Global code (http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3)
Трассировка стека Firefox выглядит так:
throwError@http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9 @http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3
Формат Safari похож на формат Firefox, но немного отличается:
throwError@http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:18 global code@http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:13
Там та же основная информация, но формат другой.
Также обратите внимание, что в примере с Safari, помимо формата, отличного от формата Chrome, номера столбцов отличаются от форматов Chrome и Firefox. Номера столбцов также могут больше отклоняться в различных ситуациях ошибок — например, в коде (function namedFunction() { throwError(); })();
, Chrome сообщит столбец для вызова функции throwError()
, а IE11 сообщит номер столбца как начало строки. Эти различия вернутся в игру позже, когда серверу потребуется проанализировать трассировку стека на предмет обнаруженных ошибок и деобфусцировать запутанные трассировки стека.
См. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack для получения дополнительной информации о свойстве стека ошибок. При доступе к свойству Error.stack Chrome включает сообщение об ошибке как часть стека, а Safari 10+ — нет.
Формат трассировок стека различается в зависимости от браузера по форме и номерам столбцов.
Если углубляться в подробности, то есть множество нюансов, связанных с форматами трассировки, которые обсуждаются в следующих разделах.
По умолчанию анонимные функции не имеют имени и отображаются либо как пустая строка, либо как «Анонимная функция» в именах функций в трассировке стека (в зависимости от браузера). Чтобы улучшить отладку, вам следует добавить имя ко всем функциям, чтобы оно отображалось во фрейме стека. Самый простой способ сделать это — гарантировать, что анонимным функциям указываются имена, даже если это имя больше нигде не используется. Например:
setTimeout (имя функцииOfTheAnonymousFunction() {...}, 0);
Это приведет к тому, что трассировка стека будет идти от:
at http://mknichel.github.io/javascript-errors/javascript-errors.js:125:17
к
at nameOfTheAnonymousFunction (http://mknichel.github.io/javascript-errors/javascript-errors.js:121:31)
В Safari это будет выглядеть так:
https://mknichel.github.io/javascript-errors/javascript-errors.js:175:27
к
nameOfTheAnonymousFunction@https://mknichel.github.io/javascript-errors/javascript-errors.js:171:41
Этот метод гарантирует, что nameOfTheAnonymousFunction
появится в кадре для любого кода внутри этой функции, что значительно упрощает отладку. Дополнительную информацию см. на странице http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/#toc-debugging-tips.
Браузеры также будут использовать имя переменной или свойства, которому назначена функция, если сама функция не имеет имени. Например, в
вар fnVariableName = function() { ... };
браузеры будут использовать fnVariableName
в качестве имени функции в трассировках стека.
at throwError (http://mknichel.github.io/javascript-errors/javascript-errors.js:27:9) at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37)
Более того, если эта переменная определена внутри другой функции, все браузеры будут использовать только имя переменной в качестве имени функции в трассировке стека, за исключением Firefox, который будет использовать другую форму, объединяющую имя внешняя функция с именем внутренней переменной. Пример:
функция throwErrorFromInnerFunctionAssignedToVariable() { var fnVariableName = function() { throw new Error("foo"); }; fnVariableName();}
будет производить в Firefox:
throwErrorFromInnerFunctionAssignedToVariable/fnVariableName@http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37
В других браузерах это будет выглядеть так:
at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37)
Firefox использует другой текст фрейма стека для функций, определенных внутри другой функции.
Отображаемое имя функции также можно установить с помощью свойства displayName
во всех основных браузерах, кроме IE11. В этих браузерах имя displayName будет отображаться в отладчике devtools, но во всех браузерах, кроме Safari, оно не будет использоваться в трассировках стека ошибок (Safari отличается от остальных тем, что также использует displayName в трассировке стека, связанной с ошибкой).
var someFunction = function() {};someFunction.displayName = " # Более подробное описание функции.";
Официальной спецификации свойства displayName не существует, но оно поддерживается всеми основными браузерами. См. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/displayName и http://www.alertdebugging.com/2009/04/29/building-a-better. -javascript-profiler-with-webkit/ для получения дополнительной информации о displayName.
IE11 не поддерживает свойство displayName.
Safari использует свойство displayName в качестве имени символа в трассировках стека ошибок.
Если сообщение об ошибке сообщается без трассировки стека (более подробную информацию о том, когда это может произойти, см. ниже), то можно программно записать трассировку стека.
В Chrome это очень легко сделать с помощью API Error.captureStackTrace
. См. https://github.com/v8/v8/wiki/Stack%20Trace%20API для получения дополнительной информации об использовании этого API.
Например:
функция ignoreThisFunctionInStackTrace() { вар ошибка = новая ошибка (); Error.captureStackTrace(ошибка, ignoreThisFunctionInStackTrace); вернуть ошибку.стек;}
В других браузерах трассировку стека также можно собрать, создав новую ошибку и обратившись к свойству стека этого объекта:
var err = новая ошибка(''); return err.stack;
Однако IE10 заполняет трассировку стека только тогда, когда действительно возникает ошибка:
пытаться { выбросить новую ошибку('');} catch (e) { вернуть электронный стек;}
Если ни один из этих подходов не работает, то можно создать приблизительную трассировку стека без номеров строк и столбцов, перебирая объект arguments.callee.caller
— однако это не будет работать в строгом режиме ES5 и не рекомендуется.
Асинхронные точки очень часто вставляются в код JavaScript, например, когда код использует setTimeout
или с помощью Promises. Эти асинхронные точки входа могут вызвать проблемы для трассировки стека, поскольку они вызывают формирование нового контекста выполнения, и трассировка стека снова начинается с нуля.
Chrome DevTools поддерживает асинхронную трассировку стека, или, другими словами, позволяет убедиться, что трассировка стека ошибки также показывает кадры, которые произошли до введения точки асинхронности. При использовании setTimeout будет зафиксировано, кто вызвал функцию setTimeout, которая в конечном итоге привела к ошибке. Дополнительную информацию см. на странице http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/.
Трассировка асинхронного стека будет выглядеть так:
throwError @ throw-error.js:2 setTimeout (async) throwErrorAsync @ throw-error.js:10 (anonymous function) @ throw-error-basic.html:14
Трассировка асинхронного стека сейчас поддерживается только в Chrome DevTools, только для исключений, которые создаются при открытии DevTools. Трассировки стека, доступные из объектов Error в коде, не будут содержать трассировку асинхронного стека как часть.
В некоторых случаях возможно полизаполнение асинхронных трассировок стека, но это может привести к значительному снижению производительности вашего приложения, поскольку захват трассировки стека обходится недешево.
Только Chrome DevTools изначально поддерживает асинхронную трассировку стека.
Трассировки стека для кода, который был оценен или встроен в HTML-страницу, будут использовать URL-адрес страницы и номера строк/столбцов для исполняемого кода.
Например:
at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9) at http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3
Если эти сценарии на самом деле взяты из сценария, который был встроен в целях оптимизации, то номера URL, строк и столбцов будут неправильными. Чтобы обойти эту проблему, Chrome и Firefox поддерживают аннотацию //# sourceURL=
(Safari, Edge и IE ее не поддерживают). URL-адрес, указанный в этой аннотации, будет использоваться в качестве URL-адреса для всех трассировок стека, а номер строки и столбца будет вычисляться относительно начала тега <script>
, а не HTML-документа. Для той же ошибки, что и выше, использование аннотации sourceURL со значением «inline.js» приведет к созданию трассировки стека, которая выглядит следующим образом:
at throwError (http://mknichel.github.io/javascript-errors/inline.js:8:9) at http://mknichel.github.io/javascript-errors/inline.js:12:3
Это действительно удобный метод, позволяющий убедиться, что трассировка стека по-прежнему правильна даже при использовании встроенных скриптов и Eval.
http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl описывает аннотацию sourceURL более подробно.
Safari, Edge и IE не поддерживают аннотацию sourceURL для именования встроенных скриптов и оценочных файлов. Если вы используете встроенные сценарии в IE или Safari и запутываете свой код, вы не сможете деобфусцировать ошибки, возникающие из этих сценариев.
Вплоть до Chrome 42 Chrome некорректно вычислял номера строк для встроенных скриптов, использующих аннотацию sourceURL. См. https://bugs.chromium.org/p/v8/issues/detail?id=3920 для получения дополнительной информации.
Номера строк для кадров стека из встроенных сценариев неверны, если используется аннотация sourceURL, поскольку они относятся к началу HTML-документа, а не к началу тега встроенного сценария (что делает невозможным правильную деобфускацию). https://code.google.com/p/chromium/issues/detail?id=578269
Для кода, использующего eval, существуют и другие различия в трассировке стека, помимо того, использует ли он аннотацию sourceURL. В Chrome трассировка стека оператора, используемого в eval, может выглядеть так:
Error: Error from eval at evaledFunction (eval at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3), <anonymous>:1:36) at eval (eval at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3), <anonymous>:1:68) at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3)
В MS Edge и IE11 это будет выглядеть так:
Error from eval at evaledFunction (eval code:1:30) at eval code (eval code:1:2) at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3)
В Сафари:
Error from eval evaledFunction eval code eval@[native code] evalError@http://mknichel.github.io/javascript-errors/javascript-errors.js:137:7
и в Firefox:
Error from eval evaledFunction@http://mknichel.github.io/javascript-errors/javascript-errors.js line 137 > eval:1:36 @http://mknichel.github.io/javascript-errors/javascript-errors.js line 137 > eval:1:11 evalError@http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3
Эти различия могут затруднить синтаксический анализ кода оценки во всех браузерах.
Каждый браузер использует свой формат трассировки стека для ошибок, произошедших внутри eval.
Ваш код JavaScript также можно вызывать непосредственно из собственного кода. Хорошим примером является Array.prototype.forEach
: вы передаете функцию в forEach
, и движок JS вызывает эту функцию за вас.
функция throwErrorWithNativeFrame() { вар обр = [0, 1, 2, 3]; arr.forEach(функция с именемFn(значение) {throwError(); });}
Это создает разные трассировки стека в разных браузерах. Chrome и Safari добавляют имя собственной функции в трассировку стека в виде отдельного фрейма, например:
(Chrome) at namedFn (http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5) at Array.forEach (native) at throwErrorWithNativeFrame (http://mknichel.github.io/javascript-errors/javascript-errors.js:152:7) (Safari) namedFn@http://mknichel.github.io/javascript-errors/javascript-errors.js:153:15 forEach@[native code] throwErrorWithNativeFrame@http://mknichel.github.io/javascript-errors/javascript-errors.js:152:14 (Edge) at namedFn (http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5) at Array.prototype.forEach (native code) at throwErrorWithNativeFrame (http://mknichel.github.io/javascript-errors/javascript-errors.js:152:7)
Однако Firefox и IE11 не показывают, что forEach
был вызван как часть стека:
namedFn@http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5 throwErrorWithNativeFrame@http://mknichel.github.io/javascript-errors/javascript-errors.js:152:3
Некоторые браузеры включают фреймы собственного кода в трассировку стека, а другие — нет.
Чтобы обнаружить, что в вашем приложении возникла ошибка, некоторый код должен иметь возможность отловить эту ошибку и сообщить о ней. Существует несколько методов обнаружения ошибок, каждый из которых имеет свои плюсы и минусы.
window.onerror
— один из самых простых и лучших способов начать ловить ошибки. При назначении window.onerror
функции любая ошибка, не обнаруженная другой частью приложения, будет сообщаться этой функции вместе с некоторой информацией об ошибке. Например:
window.onerror = function(msg, url, line, col, err) { console.log('В приложении произошла ошибка:' + msg); console.log('Трассировка стека: ' + err.stack);}
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror описывает это более подробно.
Исторически этот подход имел несколько проблем:
Объект ошибки не предоставлен
Пятый аргумент функции window.onerror
должен быть объектом Error. Это было добавлено в спецификацию WHATWG в 2013 году: https://html.spec.whatwg.org/multipage/webappapis.html#errorevent. Chrome, Firefox и IE11 теперь правильно предоставляют объект Error (вместе со свойством критического стека), а Safari, MS Edge и IE10 — нет. Это работает в Firefox начиная с Firefox 14 (https://bugzilla.mozilla.org/show_bug.cgi?id=355430) и в Chrome с конца 2013 года (https://mikewest.org/2013/08/debugging-runtime-errors). -с-окном-ошибкой, https://code.google.com/p/chromium/issues/detail?id=147127). В Safari 10 появилась поддержка объекта Error в window.onerror.
Safari (версии ниже 10), MS Edge и IE10 не поддерживают объект Error с трассировкой стека в window.onerror.
Междоменная очистка
В Chrome ошибки, поступающие из другого домена в обработчике window.onerror, будут преобразованы в «Ошибка сценария.», «», 0. Обычно это нормально, если вы действительно не хотите обрабатывать ошибку, если она исходит от скрипт, который вас не волнует, поэтому приложение может отфильтровывать ошибки, которые выглядят так. Однако этого не происходит в Firefox, Safari или IE11, а Chrome не делает этого для блоков try/catch, которые оборачивают нарушающий код.
Если вы хотите получать ошибки в window.onerror
в Chrome с полной точностью из междоменных сценариев, эти ресурсы должны предоставить соответствующие заголовки перекрестного происхождения. Дополнительную информацию см. на https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror.
Chrome — единственный браузер, который устраняет ошибки другого происхождения. Обязательно отфильтруйте их или установите соответствующие заголовки.
Расширения Chrome
В старых версиях Chrome расширения Chrome, установленные на компьютере пользователя, также могли вызывать ошибки, о которых сообщается в window.onerror. Это исправлено в новых версиях Chrome. См. специальный раздел «Расширения Chrome» ниже.
API window.addEventListener("error")
работает так же, как API window.onerror. См. http://www.w3.org/html/wg/drafts/html/master/webappapis.html#runtime-script-errors для получения дополнительной информации об этом подходе.
Перехват ошибок с помощью window.onerror не предотвращает появление этой ошибки в консоли DevTools. Скорее всего, это правильное поведение для разработки, поскольку разработчик может легко увидеть ошибку. Если вы не хотите, чтобы эти ошибки отображались в рабочей среде для конечных пользователей, можно вызвать e.preventDefault()
при использовании подхода window.addEventListener.
window.onerror — лучший инструмент для обнаружения и сообщения об ошибках JS. Рекомендуется отправлять на сервер только ошибки JS с действительными объектами Error и трассировками стека, в противном случае ошибки может быть сложно исследовать или вы можете получить много спама от расширений Chrome или междоменных скриптов.
Учитывая приведенный выше раздел, к сожалению, невозможно полагаться на window.onerror
во всех браузерах для сбора всей информации об ошибках. Для локального перехвата исключений очевидным выбором является блок try/catch. Также возможно обернуть все файлы JavaScript в блок try/catch для сбора информации об ошибках, которую невозможно перехватить с помощью window.onerror. Это улучшает ситуацию для браузеров, которые не поддерживают window.onerror, но имеет и некоторые недостатки.
Блок try/catch не фиксирует все ошибки в программе, например ошибки, возникающие из асинхронного блока кода через window.setTimeout
. Try/catch можно использовать с защищенными точками входа, чтобы заполнить пробелы.
Блоков try/catch, охватывающих все приложение, недостаточно для перехвата всех ошибок.
Старые версии V8 (и, возможно, другие JS-движки), функции, содержащие блок try/catch, не будут оптимизированы компилятором (http://www.html5rocks.com/en/tutorials/speed/v8/). Chrome исправил это в TurboFan (https://codereview.chromium.org/1996373002).
«Точкой входа» в JavaScript является любой API браузера, который может начать выполнение вашего кода. Примеры включают setTimeout
, setInterval
, прослушиватели событий, XHR, веб-сокеты или обещания. Ошибки, возникающие из этих точек входа, будут перехватываться window.onerror, но в браузерах, которые не поддерживают полный объект Error в window.onerror, необходим альтернативный механизм для перехвата этих ошибок, поскольку упомянутый метод try/catch выше их тоже не поймает.
К счастью, JavaScript позволяет оборачивать эти точки входа так, чтобы перед вызовом функции можно было вставить блок try/catch для перехвата любых ошибок, возникающих в коде.
Для каждой точки входа потребуется немного другой код для защиты точки входа, но суть методологии такова:
функция ProtectEntryPoint(fn) { return function protectedFn() {try { return fn();} catch (e) { // Обработка ошибки.} }}_oldSetTimeout = window.setTimeout;window.setTimeout = функция protectedSetTimeout(fn, time) { return _oldSetTimeout.call(window, protectedEntryPoint(fn), time);};
К сожалению, ошибки, возникающие в Promises, могут легко остаться незамеченными и не сообщенными. Об ошибках, которые происходят в Promise, но не обрабатываются путем подключения обработчика отклонения, больше нигде не сообщается — о них не сообщается в window.onerror
. Даже если к промису присоединяется обработчик отклонения, сам этот код должен вручную сообщать об этих ошибках, чтобы они были зарегистрированы. Дополнительную информацию см. на странице http://www.html5rocks.com/en/tutorials/es6/promises/#toc-error-handling. Например:
window.onerror = функция(...) { // Это никогда не будет вызвано кодом Promise.};var p = new Promise(...);p.then(function() { throw new Error("Эта ошибка нигде не будет обработана.");});var p2 = new Promise(...);p2.then(function() { throw new Error("Эта ошибка будет обработана в цепочке.");}).catch(function(error) { // Показать сообщение об ошибке пользователю // Этот код должен вручную сообщить об ошибке, чтобы она была зарегистрирована на сервере, если это применимо.});
Один из подходов к сбору дополнительной информации — использовать защищенные точки входа для обертывания вызовов методов Promise с помощью try/catch для сообщения об ошибках. Это может выглядеть так:
вар _oldPromiseThen = Promise.prototype.then; Promise.prototype.then = function protectedThen(callback, errorHandler) {return _oldPromiseThen.call(this, protectedEntryPoint(callback), protectedEntryPoint(errorHandler)); };
К сожалению, ошибки Promises по умолчанию не обрабатываются.
Реализации обещаний, такие как Q, Bluebird и Closure, обрабатывают ошибки по-разному, что лучше, чем обработка ошибок в реализации обещаний в браузере.
В Q вы можете «завершить» цепочку обещаний, вызвав .done()
, который гарантирует, что, если ошибка не была обработана в цепочке, она будет выдана повторно и о ней будет сообщено. См. https://github.com/kriskowal/q#handling-errors.
В Bluebird необработанные отклонения регистрируются и о них немедленно сообщается. См. http://bluebirdjs.com/docs/features.html#surfacing-unhandled-errors.
В реализации goog.Promise Closure необработанные отклонения регистрируются и сообщаются, если ни одна цепочка в Promise не обрабатывает отказ в течение настраиваемого интервала времени (чтобы позволить коду позже в программе добавить обработчик отклонения).
В приведенном выше разделе трассировки асинхронного стека обсуждается, что браузеры не собирают информацию о стеке при наличии асинхронного перехвата, такого как вызов Promise.prototype.then
. Полифиллы промисов позволяют фиксировать точки трассировки асинхронного стека, что значительно упрощает диагностику ошибок. Этот подход является дорогостоящим, но он может быть действительно полезен для сбора большего количества отладочной информации.
В Q вызовите Q.longStackSupport = true;
. См. https://github.com/kriskowal/q#long-stack-traces.
В Bluebird вызовите Promise.longStackTraces()
где-нибудь в приложении. См. http://bluebirdjs.com/docs/features.html#long-stack-traces.
В Closure установите для goog.Promise.LONG_STACK_TRACES
значение true.
В Chrome 49 добавлена поддержка событий, которые отправляются при отклонении обещания. Это позволяет приложениям подключаться к ошибкам Promise, чтобы обеспечить их централизованное сообщение вместе с остальными ошибками.
window.addEventListener('unhandledrejection', event => { // event.reason содержит причину отклонения. Когда выдается ошибка, это объект Error.});
Дополнительную информацию см. на https://googlechrome.github.io/samples/promise-rejection-events/ и https://www.chromestatus.com/feature/4805872211460096.
Это не поддерживается ни в одном другом браузере.
Веб-работники, в том числе выделенные работники, общие работники и сервисные работники, сегодня становятся все более популярными в приложениях. Поскольку все эти воркеры представляют собой отдельные скрипты главной страницы, каждому из них нужен собственный код обработки ошибок. Рекомендуется, чтобы каждый рабочий сценарий устанавливал собственный код обработки ошибок и отчетов для максимальной эффективности обработки ошибок рабочих процессов.
Выделенные веб-воркеры выполняются в другом контексте выполнения, чем главная страница, поэтому ошибки воркеров не перехватываются вышеуказанными механизмами. Необходимо предпринять дополнительные шаги для отслеживания ошибок рабочих на странице.
При создании работника для нового работника можно установить свойство onerror:
вар рабочий = новый работник('worker.js');worker.onerror = функция(errorEvent) {... };
Это определено в https://html.spec.whatwg.org/multipage/workers.html#handler-abstractworker-onerror. Функция onerror
на рабочем столе имеет другую сигнатуру, чем описанная выше window.onerror
. Вместо того, чтобы принимать 5 аргументов, worker.onerror
принимает один аргумент: объект ErrorEvent
. API для этого объекта можно найти по адресу https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent. Он содержит сообщение, имя файла, строку и столбец, но сегодня ни один стабильный браузер не содержит объект «Error», содержащий трассировку стека (errorEvent.error имеет значение null). Поскольку этот API выполняется в области родительской страницы, было бы полезно использовать тот же механизм отчетов, что и родительская страница; к сожалению, из-за отсутствия трассировки стека этот API имеет ограниченное применение.
Внутри JS, запускаемого воркером, вы также можете определить API onerror, который следует обычному API window.onerror: https://html.spec.whatwg.org/multipage/webappapis.html#onerroreventhandler. В рабочем коде:
self.onerror = функция (сообщение, имя файла, строка, столбец, ошибка) { ... };
Обсуждение этого API в основном следует за приведенным выше обсуждением window.onerror. Однако стоит отметить 2 примечательных момента:
Firefox и Safari не сообщают об объекте «ошибка» в качестве 5-го аргумента функции, поэтому эти браузеры не получают трассировку стека от рабочего процесса (Chrome, MS Edge и IE11 получают трассировку стека). Защищенные точки входа для функции onmessage
в рабочем потоке можно использовать для сбора информации трассировки стека для этих браузеров.
Поскольку этот код выполняется внутри работника, код должен выбрать, как сообщить об ошибке обратно на сервер: он должен либо использовать postMessage
для передачи ошибки обратно на родительскую страницу, либо установить механизм сообщения об ошибках XHR (подробнее обсуждается ниже) в сам работник.
В Firefox, Safari и IE11 (но не в Chrome) функция window.onerror
родительской страницы также будет вызываться после того, как будет вызвана собственная ошибка работника и прослушиватель событий onerror, установленный страницей. Однако этот window.onerror также не будет содержать объект ошибки и, следовательно, не будет иметь трассировки стека. Эти браузеры также должны позаботиться о том, чтобы не сообщать об ошибках рабочих процессов несколько раз.
Chrome и Firefox поддерживают API SharedWorker для совместного использования рабочего процесса на нескольких страницах. Поскольку рабочий объект является общим, он не привязан исключительно к одной родительской странице; это приводит к некоторым различиям в способах обработки ошибок, хотя SharedWorker в основном использует ту же информацию, что и выделенный веб-воркер.
В Chrome при возникновении ошибки в SharedWorker будет вызываться только собственная обработка ошибок работника внутри самого кода работника (например, если они установили self.onerror
). window.onerror
родительской страницы вызываться не будет, а Chrome не поддерживает унаследованный AbstractWorker.onerror
, который можно вызвать на родительской странице, как определено в спецификации.
В Firefox это поведение отличается. Ошибка в общем рабочем процессе приведет к вызову window.onerror родительской страницы, но объект ошибки будет иметь значение null. Кроме того, Firefox поддерживает свойство AbstractWorker.onerror
, поэтому родительская страница может присоединить к рабочему объекту собственный обработчик ошибок. Однако при вызове этого обработчика ошибок объект ошибки будет нулевым, поэтому трассировка стека не будет, поэтому его использование ограничено.
Обработка ошибок для общих рабочих процессов зависит от браузера.
Service Workers — это совершенно новая спецификация, которая в настоящее время доступна только в последних версиях Chrome и Firefox. Эти работники следуют тем же обсуждениям, что и выделенные веб-работники.
Сервис-воркеры устанавливаются путем вызова функции navigator.serviceWorker.register
. Эта функция возвращает обещание, которое будет отклонено, если при установке сервис-воркера произошла ошибка, например, во время инициализации. Эта ошибка будет содержать только строковое сообщение и ничего больше. Кроме того, поскольку промисы не сообщают об ошибках обработчикам window.onerror
, самому приложению придется добавить в промис блок catch, чтобы перехватить ошибку.
navigator.serviceWorker.register('service-worker-installation-error.js').catch(function(error) { // тип ошибки: строка});
Как и другие воркеры, сервис-воркеры могут установить внутри сервис-воркеров функцию self.onerror
для обнаружения ошибок. Об ошибках установки в сервис-воркере будет сообщено функции onerror, но, к сожалению, они не будут содержать объект ошибки или трассировку стека.
API сервис-воркера содержит свойство onerror, унаследованное от интерфейса AbstractWorker, но Chrome ничего не делает.