Время на прочтение
7 мин
Количество просмотров 9.3K
Автоматически генерируемые ошибки JavaScript – почему они происходят
Первый шаг к пониманию ошибок JavaScript – разобраться, откуда они берутся. Большинство ошибок JavaScript, встречающихся в природе, автоматически генерируются самим движком JavaScript. Существует множество типов ошибок, но, как правило, все они относятся к одному из трех классов.
TypeError
Один из наиболее распространенных классов ошибок. Такая ошибка возникает, когда некоторое значение относится не к тому типу, к которому должно относиться. Часто это возникает, когда код вызывает как функцию некоторую сущность, функцией не являющуюся. Это может быть неопределенное (“undefined”) или какое-нибудь другое значение.
window.foo()
// => TypeError: window.foo это не функция
[].length
// => 0
[].length()
// => TypeError: array.length это не функция
Еще один распространенный случай, в котором возникает TypeError – при попытке обратиться к свойству неопределенного значения.
window.foo.bar
// => TypeError: Невозможно прочитать свойство 'bar' у undefined
SyntaxError
Эти ошибки возникают, когда движок JavaScript разбирает скрипт и встречает синтаксически неверный код. Если в файле JavaScript содержится синтаксическая ошибка, то никакой код из него не выполнится.
console.log('hello')
notValid(
Мало того, что этот код выдаст ошибку, но и console.log перед неверным кодом даже не запустится.
ReferenceError
Такие ошибки возникают, когда код ссылается на значение, отсутствующее в актуальной области видимости. Например:
console.log(somethingMadeUp)
// => ReferenceError: somethingMadeUp не определено
Выбрасывание ошибок вручную
Не все ошибки случайны. Их можно спровоцировать и намеренно. Когда приложение работает неправильно, предпочтительно сделать так, чтобы оно рушилось громко, явно и четко. В противном случае причина проблемы может оставаться невыясненной, либо, хуже того, даже незамеченной для разработчика.
Простейший способ вручную спровоцировать ошибку – при помощи оператора throw:
throw 'Invalid input';
В таком случае автоматически создается экземпляр объекта Error с сообщением “Invalid input”, но экземпляр ошибки также можно создать вручную и передавать его в коде.
let error = new Error('Invalid input')
// позже
throw error;
Вручную выбрасывать ошибки JS особенно удобно авторам библиотек, поскольку так можно подсказать разработчику, пользующемуся их библиотекой, как именно совершается ошибка. Например, при вызове функции с недействительным аргументом.
function sayName(name) {
if(typeof name !== 'string') {
throw new Error('name must be a string, received:' + typeof name);
}
}
Перехват ошибок в блоке try/catch
Если вы знаете, что конкретный блок кода устроен рискованно и может выбрасывать ошибку, то эту ошибку можно обернуть в блок try/catch.
try {
someCrashyFunction()
} catch(error) {
// может быть, отобразить это пользователю
// и сообщить об ошибке разработчику
Bugsnag.notify(error);
}
Блоки try/catch также можно вкладывать друг в друга. Как только ошибка будет обработана, желательно передать ее выше по стеку вызовов, чтобы там она могла быть выброшена повторно.
try {
someCrashyFunction()
} catch(error) {
// Обработать ошибку здесь:
// ...
// а затем передать вверх по цепочке
throw error;
}
Давайте включим глобальную обработку ошибок и выловим их все
Даже в самый аккуратно написанный код иногда могут вкрадываться ошибки. Это нормально. Ошибки случаются. Но, когда они случаются, о них важно быстро узнавать. Именно для такой цели пригодится инструмент для отчетов об ошибках, например, Bugsnag.
Как устроена глобальная обработка ошибок
Чтобы выловить и обработать все ошибки JavaScript, которые могут возникнуть в браузерном сеансе, можно прибегнуть к обработчику событий window.onerror. Так можно задать глобальный обработчик для всех необработанных ошибок, которые могли бы всплыть в коде. Именно такое событие применяется в библиотеке Bugsnag для сообщения о неотловленных ошибках в браузерных JavaScript-приложениях.
В среде Node нет объекта window, поэтому аналогичный функционал связан с использованием process.on(‘unhandledException, callback).
Глобальная обработка ошибок не может заменить детализированного контроля, который достигается при помощи операторов try/catch. Глобальная обработка ошибок применяется только для подстраховки в качестве средства от тех исключений, которые проскользнут через передовые линии защиты. Устраивая обработку ошибок ближе к источнику потенциальной проблемы, мы, пожалуй, будем лучше представлять, как справиться с ошибкой и как восстановиться после нее еще до того, как пользователь заметит проблему. А если ошибка и проползет через какую-нибудь щель, то мы можем спокойно рассчитывать на то, что глобальный обработчик ошибок все равно предъявит нам возникшие проблемы.
Неисправные промисы
С появлением ES2015 промисы в JavaScript получили поддержку на уровне сущностей первого класса, и это позволило значительно повысить ясность асинхронного кода. Один недостаток промисов заключается в том, что они часто проглатывают ошибки, возникающие у них в методе.then(). Если в этом методе будет сгенерирована ошибка, то она никогда не всплывет на уровень глобального обработчика ошибок.
fetch('https://my-api.endpoint')
.then((response) => {
response.thisMethodDoesNotExist() // эта ошибка будет поглощена
doSomethingElse() // этот код никогда не будет выполнен
})
Вот почему рекомендуется всегда добавлять оператор catch во все цепочки промисов, так, чтобы могли быть обработаны любые ошибки.
fetch('https://my-api.endpoint')
.then((response) => {
response.thisMethodDoesNotExist()
doSomethingElse() // этот код никогда не будет выполнен
})
.catch((error) => {
console.error(error)
// # => response.thisMethodDoesNotExist это не функция
Bugsnag.notify(error)
// показать ошибку пользователю
});
Так решается проблема с невидимыми ошибками. Но и у этого подхода есть некоторые недостатки. Во-первых, довольно обременительно писать такой код обработки ошибок для каждого используемого нами промиса. Во-вторых, если ошибка возникнет в операторе catch, она также будет поглощена, и мы вернемся к проблеме, с которой начинали. Чтобы обойти эту проблему, можно воспользоваться глобальным обработчиком, включающимся в случае отклонения необработанных промисов.
window.addEventListener("unhandledrejection", (event) => {
console.error(event.reason);
// здесь сообщаем об ошибке
});
Теперь любой отказавший промис, у которого нет явного обработчика catch, спровоцирует событие unhandledrejection.
Свойства ошибки
Как только ошибка схвачена, ее можно проинспектировать, чтобы извлечь из нее полезную информацию. В данном случае наиболее важны свойства name, message и stack.
Первые фрагменты полезной информации об ошибке – это name и message. Именно эти поля отображаются в сообщениях об ошибках и выводятся в консоль браузера.
Сообщение об ошибке устанавливается при ее инициализации.
let error = new Error('This is my message')
console.log(error.message)
// => это мое сообщение
По умолчанию ошибка имеет то же имя, что и функция конструктора. Поэтому, когда ошибка создается при помощи новой Error(‘oh no!’) или throw(‘oh no!’), она называется “Error”. Если создать ошибку при помощи новой TypeError(‘oh no!’), то она будет называться “TypeError”. Имя ошибки можно переопределить, просто установив другое имя.
let myError = new Error('some message');
myError.name = 'ValidationError';
throw myError;
Здесь мы переименовали ошибку в ValidationError, и, например, в дашборде Bugsnag это будет отражено. Но некоторые браузеры (напр., Chrome), все равно будут выводить в консоль просто “Error”. Чтобы справиться с этой проблемой, можно применять пользовательские классы ошибок, о которых мы поговорим ниже в этой статье.
Стектрейсы
Свойство Error.prototype.stack содержит стектрейс ошибки. Стректрейс хранится вместе с ошибкой в виде обычной последовательности символов, где разделителем между всеми функциями в стеке служат символы перехода на новую строку.
Важно отметить, что структура стектрейса зависит от того, где была инициализирована ошибка, а не где она выброшена. Таким образом, если ошибка создается в functionA и возвращается от нее, а затем выбрасывается в functionB, то на вершине стектрейса будет functionA.
Вполне вероятно, что вы минифицируете ваш код JavaScript. В таком случае строки в стектрейсе не будут совпадать со строками в оригинальном файле с исходным кодом. Чтобы найти оригинальный исходный код, используются специальные карты исходников, по которым стектрейс можно отыскать и «перевести». Об этом рассказано в статье the Anatomy of source maps.
Создание пользовательских типов ошибок
Иногда бывает полезно создавать пользовательские типы ошибок в дополнение к тем, которые уже встроены в язык JavaScript. Применить их можно, например, для того, чтобы ошибки различных типов обрабатывались в приложении по-разному.
Например, в приложении Node нам стоило бы предусмотреть специальный класс ошибок, возникающих при валидации запросов к API. Если отловлена такая ошибка валидации, то приложению будет известно, что реагировать на нее нужно кодом состояния HTTP 400.
При пользовательских ошибках также можно отловить дополнительные данные об ошибке, специфичные именно для данного класса ошибок.
Благодаря классам ES6, работа по определению пользовательских типов ошибок крайне упростилась. Например, если бы мы хотели выбрасывать ошибку конкретного типа для недействительных полей, то могли бы определить эту ситуацию вот так.
class ValidationError extends Error {
constructor(field, reason) {
super(reason);
this.field = field;
this.reason = reason;
// следующая строка важна, так как сообщает, что конструктор ValidationError
// не входит в состав результирующего стектрейса
Error.captureStackTrace(this, ValidationError);
}
// также в этом классе можно определить пользовательские методы
prettyMessage() {
return `ValidationError: [${this.fields}] reason: ${this.reason}`;
// исключение: "ValidationError: [age] причина: должно быть числом"
}
}
Затем код ошибки может воспользоваться instanceof, чтобы определить, ошибка какого типа была выброшена, и отреагировать соответствующим образом. Например, в приложении на Express.js это достижимо при помощи специализированного промежуточного ПО.
app.use(function errorHandler (error, req, res, next) {
if (error instance of ValidationError) {
// выдать в ответ код 400 и включить важные детали об ошибке
return res.status(400).json({
type: error.name,
message: error.prettyMessage(),
field: error.field,
});
} else {
// Это ошибка другого рода, пусть с ней разберется обработчик ошибок, заданный по умолчанию
next(error)
}
})
Хотя в этом примере и используется промежуточное ПО из Express.js, подобный подход применим и в других разновидностях приложений JavaScript, с использованием обычного try/catch.
try {
submitForm();
} catch (error) {
if (error instanceof ValidationError) {
// показать ошибку пользователю
displayErrorMessage(error.prettyMessage());
} else {
// передать ее обработчику ошибок, заданному по умолчанию
throw error;
}
}
Без пользовательских классов ошибок специализированная обработка ошибок в таком стиле была бы гораздо сложнее. Требовалось бы идти на уловки, например, сравнивать сообщение об ошибке с каким-нибудь собственноручно написанным свойством. К счастью, такое сравнение с применением классов ошибок протекает гораздо более явно.
Заключение
При отказе приложения идеально сделать так, чтобы пользователь завершил работу с ним как можно более гладко. Но для разработчика приложение должно рушиться громко, так, чтобы было явно видно, какая проблема привела к отказу, и эту проблему можно было быстро проанализировать. Грамотно пользуясь предоставляемыми в JavaScript инструментами обработки ошибок, легче обрабатывать любые туманные аномалии, возникающие в приложении.
P.S.
На сайте издательства продолжается осенняя распродажа.
JavaScript Errors Handbook
This README contains information that I’ve learned over the years about dealing with JavaScript errors, reporting them to the server, and navigating through a lot of bugs that can make this all really hard. Browsers have improved in this area, but there is still room left to improve to make sure that all applications can sanely and soundly handle any error that happens.
Test cases for content found in this guide can be found at https://mknichel.github.io/javascript-errors/.
Table of Contents
- Introduction
- Anatomy of a JavaScript Error
- Producing a JavaScript Error
- Error Messages
- Stack Trace Format
- Catching JavaScript Errors
- window.onerror
- try/catch
- Protected Entry Points
- Promises
- Web Workers
- Chrome Extensions
Introduction
Catching, reporting, and fixing errors is an important part of any application to ensure the health and stability of the application. Since JavaScript code is also executed on the client and in many different browser environments, staying on top of JS Errors from your application can also be hard. There are no formal web specs for how to report JS errors which cause differences in each browser’s implementation. Additionally, there have been many bugs in browsers’ implementation of JavaScript errors as well that have made this even harder. This page navigates through these aspects of JS Errors so that future developers can handle errors better and browsers will hopefully converge on standardized solutions.
Anatomy of a JavaScript Error
A JavaScript error is composed of two primary pieces: the error message and the stack trace. The error message is a string that describes what went wrong, and the stack trace describes where in the code the error happened. JS Errors can be produced either by the browser itself or thrown by application code.
Producing a JavaScript Error
A JS Error can be thrown by the browser when a piece of code doesn’t execute properly, or it can be thrown directly by code.
For example:
In this example, a variable that is actually a number can’t be invoked as a function. The browser will throw an error like TypeError: a is not a function
with a stack trace that points to that line of code.
A developer might also want to throw an error in a piece of code if a certain precondition is not met. For example
if (!checkPrecondition()) { throw new Error("Doesn't meet precondition!"); }
In this case, the error will be Error: Doesn't meet precondition!
. This error will also contain a stack trace that points to the appropriate line. Errors thrown by the browser and application code can be handled the same.
There are multiple ways that developers can throw an error in JavaScript:
throw new Error('Problem description.')
throw Error('Problem description.')
<— equivalent to the first onethrow 'Problem description.'
<— badthrow null
<— even worse
Throwing a string or null is really not recommended since the browser will not attach a stack trace to that error, losing the context of where that error ocurred in the code. It is best to throw an actual Error object, which will contain the error message as well as a stack trace that points to the right lines of code where the error happened.
Error Messages
Each browser has its own set of messages that it uses for the built in exceptions, such as the example above for trying to call a non-function. Browsers will try to use the same messages, but since there is no spec, this is not guaranteed. For example, both Chrome and Firefox use {0} is not a function
for the above example while IE11 will report Function expected
(notably also without reporting what variable was attempted to be called).
However, browsers tend to diverge often as well. When there are multiple default statements in a switch
statement, Chrome will throw "More than one default clause in switch statement"
while Firefox will report "more than one switch default"
. As new features are added to the web, these error messages have to be updated. These differences can come into play later when you are trying to handle reported errors from obfuscated code.
You can find the templates that browsers use for error messages at:
- 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
Browsers will produce different error messages for some exceptions.
Stack Trace Format
The stack trace is a description of where the error happened in the code. It is composed of a series of frames, where each frames describe a particular line in the code. The topmost frame is the location where the error was thrown, while the subsequent frames are the function call stack — or how the code was executed to get to that point where the error was thrown. Since JavaScript is usually concatenated and minified, it is also important to have column numbers so that the exact statement can be located when a given line has a multitude of statements.
A basic stack trace in Chrome looks like:
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
Each stack frame consists of a function name (if applicable and the code was not executed in the global scope), the script that it came from, and the line and column number of the code.
Unfortunately, there is no standard for the stack trace format so this differs by browser.
Microsoft Edge and IE 11’s stack trace looks similar to Chrome’s except it explicitly lists Global code:
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’s stack trace looks like:
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’s format is similar to Firefox’s format but is also slightly different:
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
The same basic information is there, but the format is different.
Also note that in the Safari example, aside from the format being different than Chrome, the column numbers are different than both Chrome and Firefox. The column numbers also can deviate more in different error situations — for example in the code (function namedFunction() { throwError(); })();
, Chrome will report the column for the throwError()
function call while IE11 reports the column number as the start of the string. These differences will come back into play later when the server needs to parse the stack trace for reported errors and deobfuscate obfuscated stack traces.
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack for more information on the stack property of errors. When accessing the Error.stack property, Chrome does include the error message as part of the stack but Safari 10+ does not.
The format of stack traces is different by browser in form and column numbers used.
Diving in more, there are a lot of nuances to stack trace formats that are discussed in the below sections.
Naming anonymous functions
By default, anonymous functions have no name and either appear as empty string or «Anonymous function» in the function names in the stack trace (depending on the browser). To improve debugging, you should add a name to all functions to ensure it appears in the stack frame. The easiest way to do this is to ensure that anonymous functions are specified with a name, even if that name is not used anywhere else. For example:
setTimeout(function nameOfTheAnonymousFunction() { ... }, 0);
This will cause the stack trace to go from:
at http://mknichel.github.io/javascript-errors/javascript-errors.js:125:17
to
at nameOfTheAnonymousFunction (http://mknichel.github.io/javascript-errors/javascript-errors.js:121:31)
In Safari, this would go from:
https://mknichel.github.io/javascript-errors/javascript-errors.js:175:27
to
nameOfTheAnonymousFunction@https://mknichel.github.io/javascript-errors/javascript-errors.js:171:41
This method ensures that nameOfTheAnonymousFunction
appears in the frame for any code from inside that function, making debugging much easier. See http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/#toc-debugging-tips for more information.
Assigning functions to a variable
Browsers will also use the name of the variable or property that a function is assigned to if the function itself does not have a name. For example, in
var fnVariableName = function() { ... };
browsers will use fnVariableName
as the name of the function in stack traces.
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)
Even more nuanced than that, if this variable is defined within another function, all browsers will use just the name of the variable as the name of the function in the stack trace except for Firefox, which will use a different form that concatenates the name of the outer function with the name of the inner variable. Example:
function throwErrorFromInnerFunctionAssignedToVariable() { var fnVariableName = function() { throw new Error("foo"); }; fnVariableName(); }
will produce in Firefox:
throwErrorFromInnerFunctionAssignedToVariable/fnVariableName@http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37
In other browsers, this would look like:
at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37)
Firefox uses different stack frame text for functions defined within another function.
displayName Property
The display name of a function can also be set by the displayName
property in all major browsers except for IE11. In these browsers, the displayName will appear in the devtools debugger, but in all browsers but Safari, it will not be used in Error stack traces (Safari differs from the rest by also using the displayName in the stack trace associated with an error).
var someFunction = function() {}; someFunction.displayName = " # A longer description of the function.";
There is no official spec for the displayName property, but it is supported by all the major browsers. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/displayName and http://www.alertdebugging.com/2009/04/29/building-a-better-javascript-profiler-with-webkit/ for more information on displayName.
IE11 doesn’t support the displayName property.
Safari uses the displayName property as the symbol name in Error stack traces.
Programatically capturing stack traces
If an error is reported without a stack trace (see more details when this would happen below), then it’s possible to programatically capture a stack trace.
In Chrome, this is really easy to do by using the Error.captureStackTrace
API. See https://github.com/v8/v8/wiki/Stack%20Trace%20API for more information on the use of this API.
For example:
function ignoreThisFunctionInStackTrace() { var err = new Error(); Error.captureStackTrace(err, ignoreThisFunctionInStackTrace); return err.stack; }
In other browsers, a stack trace can also be collected by creating a new error and accessing the stack property of that object:
var err = new Error(''); return err.stack;
However, IE10 only populates the stack trace when the error is actually thrown:
try { throw new Error(''); } catch (e) { return e.stack; }
If none of these approaches work, then it’s possible to create a rough stack trace without line numbers or columns by iterating over the arguments.callee.caller
object — this won’t work in ES5 Strict Mode though and it’s not a recommended approach.
Async stack traces
It is very common for asynchronous points to be inserted into JavaScript code, such as when code uses setTimeout
or through the use of Promises. These async entry points can cause problems for stack traces, since they cause a new execution context to form and the stack trace starts from scratch again.
Chrome DevTools has support for async stack traces, or in other words making sure the stack trace of an error also shows the frames that happened before the async point was introduced. With the use of setTimeout, this will capture who called the setTimeout function that eventually produced an error. See http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/ for more information.
An async stack trace will look like:
throwError @ throw-error.js:2
setTimeout (async)
throwErrorAsync @ throw-error.js:10
(anonymous function) @ throw-error-basic.html:14
Async stack traces are only supported in Chrome DevTools right now, only for exceptions that are thrown when DevTools are open. Stack traces accessed from Error objects in code will not have the async stack trace as part of it.
It is possible to polyfill async stack traces in some cases, but this could cause a significant performance hit for your application since capturing a stack trace is not cheap.
Only Chrome DevTools natively supports async stack traces.
Naming inline scripts and eval
Stack traces for code that was eval’ed or inlined into a HTML page will use the page’s URL and line/column numbers for the executed code.
For example:
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
If these scripts actually come from a script that was inlined for optimization reasons, then the URL, line, and column numbers will be wrong. To work around this problem, Chrome and Firefox support the //# sourceURL=
annotation (Safari, Edge, and IE do not). The URL specified in this annotation will be used as the URL for all stack traces, and the line and column number will be computed relative to the start of the <script>
tag instead of the HTML document. For the same error as above, using the sourceURL annotation with a value of «inline.js» will produce a stack trace that looks like:
at throwError (http://mknichel.github.io/javascript-errors/inline.js:8:9)
at http://mknichel.github.io/javascript-errors/inline.js:12:3
This is a really handy technique to make sure that stack traces are still correct even when using inline scripts and eval.
http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl describes the sourceURL annotation in more detail.
Safari, Edge, and IE do not support the sourceURL annotation for naming inline scripts and evals. If you use inline scripts in IE or Safari and you obfuscate your code, you will not be able to deobfuscate errors that come from those scripts.
Up until Chrome 42, Chrome did not compute line numbers correctly for inline scripts that use the sourceURL annotation. See https://bugs.chromium.org/p/v8/issues/detail?id=3920 for more information.
Line numbers for stack frames from inline scripts are incorrect when the sourceURL annotation is used since they are relative to the start of the HTML document instead of the start of the inline script tag (making correct deobfuscation not possible). https://code.google.com/p/chromium/issues/detail?id=578269
Eval stack traces
For code that uses eval, there are other differences in the stack trace besides whether or not it uses the sourceURL annotation. In Chrome, a stack trace from a statement used in eval could look like:
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)
In MS Edge and IE11, this would look like:
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)
In Safari:
Error from eval
evaledFunction
eval code
eval@[native code]
evalError@http://mknichel.github.io/javascript-errors/javascript-errors.js:137:7
and in 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
These differences can make it hard to parse eval code the same across all browsers.
Each browser uses a different stack trace format for errors that happened inside eval.
Stack traces with native frames
Your JavaScript code can also be called directly from native code. Array.prototype.forEach
is a good example — you pass a function to forEach
and the JS engine will call that function for you.
function throwErrorWithNativeFrame() { var arr = [0, 1, 2, 3]; arr.forEach(function namedFn(value) { throwError(); }); }
This produces different stack traces in different browsers. Chrome and Safari append the name of the native function in the stack trace itself as a separate frame, such as:
(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)
However, Firefox and IE11 do not show that forEach
was called as part of the stack:
namedFn@http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5
throwErrorWithNativeFrame@http://mknichel.github.io/javascript-errors/javascript-errors.js:152:3
Some browsers include native code frames in stack traces, while others do not.
Catching JavaScript Errors
To detect that your application had an error, some code must be able to catch that error and report about it. There are multiple techniques for catching errors, each with their pros and cons.
window.onerror
window.onerror
is one of the easiest and best ways to get started catching errors. By assigning window.onerror
to a function, any error that is uncaught by another part of the application will be reported to this function, along with some information about the error. For example:
window.onerror = function(msg, url, line, col, err) { console.log('Application encountered an error: ' + msg); console.log('Stack trace: ' + err.stack); }
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror describes this in more detail.
Historically, there have been a few problems with this approach:
No Error object provided
The 5th argument to the window.onerror
function is supposed to be an Error object. This was added to the WHATWG spec in 2013: https://html.spec.whatwg.org/multipage/webappapis.html#errorevent. Chrome, Firefox, and IE11 now properly provide an Error object (along with the critical stack property), but Safari, MS Edge, and IE10 do not. This works in Firefox since Firefox 14 (https://bugzilla.mozilla.org/show_bug.cgi?id=355430) and in Chrome since late 2013 (https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror, https://code.google.com/p/chromium/issues/detail?id=147127). Safari 10 launched support for the Error object in window.onerror.
Safari (versions below 10), MS Edge, and IE10 do not support an Error object with a stack trace in window.onerror.
Cross domain sanitization
In Chrome, errors that come from another domain in the window.onerror handler will be sanitized to «Script error.», «», 0. This is generally okay if you really don’t want to process the error if it comes from a script that you don’t care about, so the application can filter out errors that look like this. However, this does not happen in Firefox or Safari or IE11, nor does Chrome do this for try/catch blocks that wrap the offending code.
If you would like to receive errors in window.onerror
in Chrome with full fidelity from cross domain scripts, those resources must provide the appropriate cross origin headers. See https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror for more information.
Chrome is the only browser that will sanitize errors that come from another origin. Take care to filter these out, or set the appropriate headers.
Chrome Extensions
In old versions of Chrome, Chrome extensions that are installed on a user’s machine could also throw errors that get reported to window.onerror. This has been fixed in newer versions of Chrome. See the dedicated Chrome Extensions section below.
window.addEventListener(«error»)
The window.addEventListener("error")
API works the same as the window.onerror API. See http://www.w3.org/html/wg/drafts/html/master/webappapis.html#runtime-script-errors for more information on this approach.
Showing errors in DevTools console for development
Catching errors via window.onerror does not prevent that error from also appearing in the DevTools console. This is most likely the right behavior for development since the developer can easily see the error. If you don’t want these errors to show up in production to end users, e.preventDefault()
can be called if using the window.addEventListener approach.
Recommendation
window.onerror is the best tool to catch and report JS errors. It’s recommended that only JS errors with valid Error objects and stack traces are reported back to the server, otherwise the errors may be hard to investigate or you may get a lot of spam from Chrome extensions or cross domain scripts.
try/catch
Given the above section, unfortunately it’s not possible to rely on window.onerror
in all browsers to capture all error information. For catching exceptions locally, a try/catch block is the obvious choice. It’s also possible to wrap entire JavaScript files in a try/catch block to capture error information that can’t be caught with window.onerror. This improves the situations for browsers that don’t support window.onerror, but also has some downsides.
Doesn’t catch all errors
A try/catch block won’t capture all errors in a program, such as errors that are thrown from an async block of code through window.setTimeout
. Try/catch can be used with Protected Entry Points to help fill in the gaps.
try/catch blocks wrapping the entire application aren’t sufficient to catch all errors.
Deoptimizations
Old versions of V8 (and potentially other JS engines), functions that contain a try/catch block won’t be optimized by the compiler (http://www.html5rocks.com/en/tutorials/speed/v8/). Chrome fixed this in TurboFan (https://codereview.chromium.org/1996373002).
Protected Entry Points
An «entry point» into JavaScript is any browser API that can start execution of your code. Examples include setTimeout
, setInterval
, event listeners, XHR, web sockets, or promises. Errors that are thrown from these entry points will be caught by window.onerror, but in the browsers that don’t support the full Error object in window.onerror, an alternative mechanism is needed to catch these errors since the try/catch method mentioned above won’t catch them either.
Thankfully, JavaScript allows these entry points to be wrapped so that a try/catch block can be inserted before the function is invoked to catch any errors thrown by the code.
Each entry point will need slightly different code to protect the entry point, but the gist of the methodology is:
function protectEntryPoint(fn) { return function protectedFn() { try { return fn(); } catch (e) { // Handle error. } } } _oldSetTimeout = window.setTimeout; window.setTimeout = function protectedSetTimeout(fn, time) { return _oldSetTimeout.call(window, protectEntryPoint(fn), time); };
Promises
Sadly, it’s easy for errors that happen in Promises to go unobserved and unreported. Errors that happen in a Promise but are not handled by attaching a rejection handler are not reported anywhere else — they do not get reported to window.onerror
. Even if a Promise attaches a rejection handler, that code itself must manually report those errors for them to be logged. See http://www.html5rocks.com/en/tutorials/es6/promises/#toc-error-handling for more information. For example:
window.onerror = function(...) { // This will never be invoked by Promise code. }; var p = new Promise(...); p.then(function() { throw new Error("This error will be not handled anywhere."); }); var p2 = new Promise(...); p2.then(function() { throw new Error("This error will be handled in the chain."); }).catch(function(error) { // Show error message to user // This code should manually report the error for it to be logged on the server, if applicable. });
One approach to capture more information is to use Protected Entry Points to wrap invocations of Promise methods with a try/catch to report errors. This might look like:
var _oldPromiseThen = Promise.prototype.then; Promise.prototype.then = function protectedThen(callback, errorHandler) { return _oldPromiseThen.call(this, protectEntryPoint(callback), protectEntryPoint(errorHandler)); };
Sadly, errors from Promises will go unhandled by default.
Error handling in Promise polyfills
Promise implementations, such as Q, Bluebird, and Closure handle errors in different ways which are better than the error handling in the browser implementation of Promises.
- In Q, you can «end» the Promise chain by calling
.done()
which will make sure that if an error wasn’t handled in the chain, it will get rethrown and reported. See https://github.com/kriskowal/q#handling-errors - In Bluebird, unhandled rejections are logged and reported immediately. See http://bluebirdjs.com/docs/features.html#surfacing-unhandled-errors
- In Closure’s goog.Promise implementation, unhandled rejections are logged and reported if no chain in the Promise handles the rejection within a configurable time interval (in order to allow code later in the program to add a rejection handler).
Long stack traces
The async stack trace section above discusses that browsers don’t capture stack information when there is an async hook, such as calling Promise.prototype.then
. Promise polyfills feature a way to capture the async stack trace points which can make diagnosing errors much easier. This approach is expensive, but it can be really useful for capturing more debug information.
- In Q, call
Q.longStackSupport = true;
. See https://github.com/kriskowal/q#long-stack-traces - In Bluebird, call
Promise.longStackTraces()
somewhere in the application. See http://bluebirdjs.com/docs/features.html#long-stack-traces. - In Closure, set
goog.Promise.LONG_STACK_TRACES
to true.
Promise Rejection Events
Chrome 49 added support for events that are dispatched when a Promise is rejected. This allows applications to hook into Promise errors to ensure that they get centrally reported along with the rest of the errors.
window.addEventListener('unhandledrejection', event => { // event.reason contains the rejection reason. When an Error is thrown, this is the Error object. });
See https://googlechrome.github.io/samples/promise-rejection-events/ and https://www.chromestatus.com/feature/4805872211460096 for more information.
This is not supported in any other browser.
Web Workers
Web workers, including dedicated workers, shared workers, and service workers, are becoming more popular in applications today. Since all of these workers are separate scripts from the main page, they each need their own error handling code. It is recommended that each worker script install its own error handling and reporting code for maximum effectiveness handling errors from workers.
Dedicated workers
Dedicated web workers execute in a different execution context than the main page, so errors from workers aren’t caught by the above mechanisms. Additional steps need to be taken to capture errors from workers on the page.
When a worker is created, the onerror property can be set on the new worker:
var worker = new Worker('worker.js'); worker.onerror = function(errorEvent) { ... };
This is defined in https://html.spec.whatwg.org/multipage/workers.html#handler-abstractworker-onerror. The onerror
function on the worker has a different signature than the window.onerror
discussed above. Instead of accepting 5 arguments, worker.onerror
takes a single argument: an ErrorEvent
object. The API for this object can be found at https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent. It contains the message, filename, line, and column, but no stable browser today contains the «Error» object that contains the stack trace (errorEvent.error is null). Since this API is executed in the parent page’s scope, it would be useful for using the same reporting mechanism as the parent page; unfortunately due to the lack of a stack trace, this API is of limited use.
Inside of the JS run by the worker, you can also define an onerror API that follows the usual window.onerror API: https://html.spec.whatwg.org/multipage/webappapis.html#onerroreventhandler. In the worker code:
self.onerror = function(message, filename, line, col, error) { ... };
The discussion of this API mostly follows the discussion above for window.onerror. However, there are 2 notable things to point out:
Firefox and Safari do not report the «error» object as the 5th argument to the function, so these browsers do not get a stack trace from the worker (Chrome, MS Edge, and IE11 do get a stack trace). Protected Entry Points for the
onmessage
function within the worker can be used to capture stack trace information for these browsers.
Since this code executes within the worker, the code must choose how to report the error back to the server: It must either use postMessage
to communicate the error back to the parent page, or install an XHR error reporting mechanism (discussed more below) in the worker itself.
In Firefox, Safari, and IE11 (but not in Chrome), the parent page’s
window.onerror
function will also be called after the worker’s own onerror and the onerror event listener set by the page has been called. However, this window.onerror will also not contain an error object and therefore won’t have a stack trace also. These browsers must also take care to not report errors from workers multiple times.
Shared workers
Chrome and Firefox support the SharedWorker API for sharing a worker among multiple pages. Since the worker is shared, it is not attached to one parent page exclusively; this leads to some differences in how errors are handled, although SharedWorker mostly follows the same information as the dedicated web worker.
In Chrome, when there is an error in a SharedWorker, only the worker’s own error handling within the worker code itself will be called (like if they set self.onerror
). The parent page’s window.onerror
will not be called, and Chrome does not support the inherited AbstractWorker.onerror
that can be called in the parent page as defined in the spec.
In Firefox, this behavior is different. An error in the shared worker will cause the parent page’s window.onerror to be called, but the error object will be null. Additionally, Firefox does support the AbstractWorker.onerror
property, so the parent page can attach an error handler of its own to the worker. However, when this error handler is called, the error object will be null so there will be no stack trace, so it’s of limited use.
Error handling for shared workers differs by browser.
Service Workers
Service Workers are a brand new spec that is currently only available in recent Chrome and Firefox versions. These workers follow the same discussion as dedicated web workers.
Service workers are installed by calling the navigator.serviceWorker.register
function. This function returns a Promise which will be rejected if there was an error installing the service worker, such as it throwing an error during initialization. This error will only contain a string message and nothing else. Additionally, since Promises don’t report errors to window.onerror
handlers, the application itself would have to add a catch block to the Promise to catch the error.
navigator.serviceWorker.register('service-worker-installation-error.js').catch(function(error) { // error typeof string });
Just like the other workers, service workers can set a self.onerror
function within the service workers to catch errors. Installation errors in the service worker will be reported to the onerror function, but unfortunately they won’t contain an error object or stack trace.
The service worker API contains an onerror property inherited from the AbstractWorker interface, but Chrome does not do anything with this property.
Worker Try/Catch
To capture stack traces in Firefox + Safari within a worker, the onmessage
function can be wrapped in a try/catch block to catch any errors that propagate to the top.
self.onmessage = function(event) { try { // logic here } catch (e) { // Report exception. } };
The normal try/catch mechanism will capture stack traces for these errors, producing an exception that looks like:
Error from worker
throwError@http://mknichel.github.io/javascript-errors/worker.js:4:9
throwErrorWrapper@http://mknichel.github.io/javascript-errors/worker.js:8:3
self.onmessage@http://mknichel.github.io/javascript-errors/worker.js:14:7
Chrome Extensions
Chrome Extensions deserve their own section since errors in these scripts can operate slightly differently, and historically (but not anymore) errors from Chrome Extensions have also been a problem for large popular sites.
Content Scripts
Content scripts are scripts that run in the context of web pages that a user visits. These scripts run in an isolated execution environment so they can access the DOM but they can not access JavaScript on the parent page (and vice versa).
Since content scripts have their own execution environment, they can assign to the window.onerror
handler in their own script and it won’t affect the parent page. However, errors caught by window.onerror
in the content script are sanitized by Chrome resulting in a «Script error.» with null filename and 0 for line and column. This bug is tracked by https://code.google.com/p/chromium/issues/detail?id=457785. Until that bug is fixed, a try/catch block or protected entry points are the only ways to catch JS errors in a content script with stack traces.
In years past, errors from content scripts would be reported to the window.onerror
handler of the parent page which could result in a large amount of spammy error reports for popular sites. This was fixed in late 2013 though (https://code.google.com/p/chromium/issues/detail?id=225513).
Errors in Chrome Extensions are sanitized before being handled by window.onerror.
Browser Actions
Chrome extensions can also generate browser action popups, which are small HTML pages that spawn when a user clicks a Chrome extension icon to the right of the URL bar. These pages can also run JavaScript, in an entirely different execution environment from everything else. window.onerror
works properly for this JavaScript.
Reporting Errors to the Server
Once the client is configured to properly catch exceptions with correct stack traces, these exceptions should be reported back to the server so they can be tracked, analyzed, and then fixed. Typically this is done with a XHR endpoint that records the error message and the stack trace information, along with any relevant client context information, such as the version of the code that’s running, the user agent, the user’s locale, and the top level URL of the page.
If the application uses multiple mechanisms to catch errors, it’s important to not report the same error twice. Errors that contain a stack trace should be preferred; errors reported without a stack trace can be hard to track down in a large application.
Опубликовано: среда, 29 марта 2023 г. в 09:06
- JavaScript
В этом руководстве мы погрузимся в обработку ошибок JavaScript, чтобы вы могли выбрасывать исключения, обнаруживать и обрабатывать собственные ошибки.
Опытные разработчики ожидают неожиданного. Если что-то может пойти не так, так оно и будет — обычно в тот момент, когда первый пользователь получает доступ к вашему новому приложению.
Некоторых ошибок веб-приложений можно избежать, например:
- Хороший редактор или линтер может отлавливать синтаксические ошибки.
- Хорошая валидация может выявить ошибки пользовательского ввода.
- Надёжные процессы тестирования могут обнаруживать логические ошибки.
И всё же ошибки остаются. Браузеры могут сбоить или не поддерживать API, который мы используем. Серверы могут дать сбой или слишком долго отвечать. Сетевое соединение может выйти из строя или стать ненадёжным. Проблемы могут быть временными, но мы не может программировать такие проблемы. Однако мы можем предвидеть проблемы, принимать меры по их устранению и повышать отказоустойчивость нашего приложения.
Отображение сообщения об ошибке — крайняя мера
В идеале пользователи никогда не должны видеть сообщения об ошибке.
Мы можем игнорировать незначительные проблемы, такие как невозможность загрузки декоративного изображения. Мы могли бы решить более серьёзные проблемы, такие как сбои сохранения данных Ajax локально и загружая их позже. Ошибка становиться необходимой только тогда, когда пользователь рискует потерять данные, предполагая, что он может что-то с этим сделать.
Поэтому необходимо выявлять ошибки по мере их возникновения и определять наилучшие действия. Вызов и перехват ошибок в приложении JavaScript поначалу может быть пугающим, но, возможно, это проще, чем вы ожидаете.
Как JavaScript обрабатывает ошибки
Когда оператор JavaScript приводит к ошибке, говорят, что он генерирует (выбрасывает) исключение. JavaScript создаёт и выбрасывает объект Error
, описывающий ошибку. Мы можем увидеть это в действии на CodePen. Если установить в десятичные разряды (decimal places) отрицательное число, мы увидим сообщение об ошибке в консоли внизу. (Обратите внимание, что мы не встраиваем CodePen в это руководство, потому что нужно иметь возможно видеть вывод консоли, чтобы этот пример имел смысл)
Результат не обновиться, и мы увидим сообщение RangeError
в консоли. Следующая функция выдаёт ошибку, когда dp
имеет отрицательно значение:
// division calculation
function divide(v1, v2, dp) {
return (v1 / v2).toFixed(dp);
}
После выдачи ошибки интерпретатор JavaScript проверяет наличие кода обработки исключений. В функции Division()
ничего нет, поэтому она проверяет вызывающую функцию:
// show result of division
function showResult() {
result.value = divide(
parseFloat(num1.value),
parseFloat(num2.value),
parseFloat(dp.value)
);
}
Интерпретатор повторяет процесс для каждой функции в стеке вызовов, пока не произойдёт одно из следующих событий:
- Он находит обработчик исключений.
- Он достигает верхнего уровня кода (что приводит к завершению программы и отображению ошибки в консоли, как показано в примере на CodePen выше).
Перехват исключений
Мы можем добавить обработчик исключений в функцию divide()
с помощью блока try...catch
:
// division calculation
function divide(v1, v2, dp) {
try {
return (v1 / v2).toFixed(dp);
}
catch(e) {
console.log(`
error name : ${ e.name }
error message: ${ e.message }
`);
return 'ERROR';
}
}
Функция выполняет код в блоке try {}
, но при возникновении исключения выполняется блок catch {}
и получает выброшенный объект ошибки. Как и прежде, попробуйте в decimal places
установить отрицательное число в этой демонстрации CodePen.
Теперь result
показывает ERROR
. Консоль показывает имя ошибки и сообщение, но это выводится оператором console.log
и не завершает работу программы.
Примечание: эта демонстрация блока
try...catch
излишняя для базовой функции, такой какdivide()
. Как мы увидим ниже, проще убедиться, чтоdp
равен нулю или больше.
Можно определить не обязательный блок finally {}
, если требуется, чтобы код запускался при выполнении кода try
или catch
:
function divide(v1, v2, dp) {
try {
return (v1 / v2).toFixed(dp);
}
catch(e) {
return 'ERROR';
}
finally {
console.log('done');
}
}
В консоль выведется done
, независимо от того, успешно ли выполнено вычисление или возникла ошибка. Блок finally
обычно выполняет действия, которые в противном случае нам пришлось бы повторять как в блоке try
, так и в блоке catch
. Например, отмену вызова API или закрытие соединения с базой данных.
Для блока try
требуется либо блок catch
, либо блок finally
, либо и то и другое. Обратите внимание, что когда блок finally
содержит оператор return
, это значение становится возвращаемым значением для всей функции; другие операторы в блоках try
или catch
игнорируются.
Вложенные обработчики исключений
Что произойдёт, если мы добавим обработчик исключений к вызывающей функции showResult()
?
// show result of division
function showResult() {
try {
result.value = divide(
parseFloat(num1.value),
parseFloat(num2.value),
parseFloat(dp.value)
);
}
catch(e) {
result.value = 'FAIL!';
}
}
Ответ… ничего! Блок catch
никогда не выполняется, потому что в функции divide()
блок catch
обрабатывает ошибку.
Тем не менее мы могли бы программно генерировать новый объект Error
в divide()
и при желании передать исходную ошибку в свойстве cause
второго аргумента:
function divide(v1, v2, dp) {
try {
return (v1 / v2).toFixed(dp);
}
catch(e) {
throw new Error('ERROR', { cause: e });
}
}
Это вызовет блок catch
в вызывающей функции:
// show result of division
function showResult() {
try {
//...
}
catch(e) {
console.log( e.message ); // ERROR
console.log( e.cause.name ); // RangeError
result.value = 'FAIL!';
}
}
Стандартные типы ошибок JavaScript
Когда возникает исключение, JavaScript создаёт и выдаёт объект, описывающий ошибку, используя один из следующих типов.
SyntaxError
Ошибка, возникающая из-за синтаксически недопустимого кода, такого как отсутствующая скобка:
if condition) { // SyntaxError
console.log('condition is true');
}
Примечание: такие языки, как C++ и Java, сообщают об ошибках синтаксиса во время компиляции. JavaScript — интерпретируемый язык, поэтому синтаксические ошибки не выявляются до тех пор, пока код не запустится. Любой хороший редактор кода или линтер могут обнаружить синтаксические ошибки до того, как мы попытаемся запустить код.
ReferenceError
Ошибка при доступе к несуществующей переменной:
function inc() {
value++; // ReferenceError
}
Опять, хороший редактор кода или линтер могут обнаружить эту проблему.
TypeError
Ошибка возникает, когда значение не соответствует ожидаемому типу, например, при вызове несуществующего метода объекта:
const obj = {};
obj.missingMethod(); // TypeError
RangeError
Ошибка возникает, когда значение не входит в набор или диапазон допустимых значений. Используемый выше метод toFixed()
генерирует эту ошибку, потому что он ожидает значение от 0 до 100:
const n = 123.456;
console.log( n.toFixed(-1) ); // RangeError
URIError
Ошибка выдаваемая функциями обработки URI, такими как encodeURI()
и decodeURI()
, при обнаружении неправильных URI:
const u = decodeURIComponent('%'); // URIError
EvalError
Ошибка возникающая при передаче строки, содержащей не валидный JavaScript код, в функцию eval()
:
eval('console.logg x;'); // EvalError
Примечание: пожалуйста, не используйте
eval()
! Выполнение произвольного кода, содержащегося в строке, возможно, созданной на основе пользовательского ввода, слишком опасно!
AggregateError
Ошибка возникает, когда несколько ошибок объединены в одну ошибку. Обычно возникает при вызове такой операции, как Promise.all()
, которая возвращает результаты нескольких промисов.
InternalError
Нестандартная ошибка (только в Firefox) возникает при возникновении внутренней ошибки движка JavaScript. Обычно это результат того, что что-то занимает слишком много памяти, например, большой массив или слишком много рекурсии
.
Error
Наконец, есть общий объект Error
, чаще всего используемый при реализации собственных исключений… о котором мы поговорим дальше.
Генерация/выбрасывание собственных исключений
Мы можем использовать throw
для генерации/выбрасывания собственных исключений, когда возникает ошибка — или должна произойти. Например:
- нашей функции не передаются валидные параметры
- ajax-запрос не возвращает ожидаемые данные
- обновление DOM завершается ошибкой, поскольку узел не существует
Оператор throw
фактически принимает любое значение или объект. Например:
throw 'A simple error string';
throw 42;
throw true;
throw { message: 'An error', name: 'MyError' };
Исключения генерируются для каждой функции в стеке вызовов до тех пор, пока они не будут перехвачены обработчиком исключений (catch
). Однако на практике мы хотим создать и сгенерировать объект Error
, чтобы он действовал идентично стандартным ошибкам, выдаваемым JavaScript.
Можно создать общий объект Error
, передав необязательное сообщение конструктору:
throw new Error('An error has occurred');
Так же Error
можно использовать как функцию, без new
. Она возвращает объект Error
, идентичный приведённому выше:
throw Error('An error has occurred');
При желании можно передать имя файла и номер строки в качестве второго и третьего параметров:
throw new Error('An error has occurred', 'script.js', 99);
В этом редко возникает необходимость, так как по умолчанию они относятся к файлу и строке, где мы вызвали объект Error
. (Также их сложно поддерживать, поскольку наши файлы меняются!)
Мы можем определить общие объекты Error
, но по возможности следует использовать стандартный тип Error
. Например:
throw new RangeError('Decimal places must be 0 or greater');
Все объекты Error
имеют следующие свойства, которые можно проверить в блоке catch
:
.name
: имя типа ошибки, напримерError
илиRangeError
..message
: сообщение об ошибке.
В Firefox поддерживаются следующие нестандартные свойства:
.fileName
: файл, в котором произошла ошибка..lineNumber
: номер строки, в которой произошла ошибка..columnNumber
: номер столбца, в котором произошла ошибка..stack
: трассировка стека со списком вызовов функций, сделанных до возникновения ошибки.
Мы можем изменить функцию divide()
так, чтобы она вызывала ошибку RangeError
, когда количество знаков после запятой не является числом, меньше нуля и больше восьми:
// division calculation
function divide(v1, v2, dp) {
if (isNaN(dp) || dp < 0 || dp > 8) {
throw new RangeError('Decimal places must be between 0 and 8');
}
return (v1 / v2).toFixed(dp);
}
Точно так же мы могли бы выдать Error
или TypeError
, когда значения делимого не является числом, чтобы предотвратить результат NaN
:
if (isNaN(v1)) {
throw new TypeError('Dividend must be a number');
}
Также можно обрабатывать делитель, который не является числом или равен нулю. JavaScript возвращает Infinity
при делении на ноль, но это может запутать пользователя. Вместо того чтобы вызывать общую ошибку, мы могли бы создать собственный тип ошибки DivByZeroError
:
// new DivByZeroError Error type
class DivByZeroError extends Error {
constructor(message) {
super(message);
this.name = 'DivByZeroError';
}
}
Затем вызывать/выбрасывать его подобным образом:
if (isNaN(v2) || !v2) {
throw new DivByZeroError('Divisor must be a non-zero number');
}
Теперь добавьте блок try...catch
к вызывающей функции showResult()
. Он сможет получить тип любой ошибки и отреагировать соответствующим образом — в данном случае, выводя сообщение об ошибке:
// show result of division
function showResult() {
try {
result.value = divide(
parseFloat(num1.value),
parseFloat(num2.value),
parseFloat(dp.value)
);
errmsg.textContent = '';
}
catch (e) {
result.value = 'ERROR';
errmsg.textContent = e.message;
console.log( e.name );
}
}
Попробуйте ввести недопустимые нечисловые, нулевые и отрицательные значения в демонстрации на CodePen.
Окончательная версия функции divide()
проверяет все входящие значения и при необходимости выдаёт соответствующую ошибку:
// division calculation
function divide(v1, v2, dp) {
if (isNaN(v1)) {
throw new TypeError('Dividend must be a number');
}
if (isNaN(v2) || !v2) {
throw new DivByZeroError('Divisor must be a non-zero number');
}
if (isNaN(dp) || dp < 0 || dp > 8) {
throw new RangeError('Decimal places must be between 0 and 8');
}
return (v1 / v2).toFixed(dp);
}
Больше нет необходимости размещать блок try...catch
вокруг финального return
, так как он никогда не должен генерировать ошибку. Если бы это произошло, JavaScript сгенерировал бы свою собственную ошибку и обработал бы её блоком catch
в showResult()
/
Ошибки асинхронной функции
Мы не можем перехватывать исключения, генерируемые асинхронными функциями на основе обратного вызова, потому что после завершения выполнения блока try...catch
выдаётся ошибка. Этот код выглядит правильно, но блок catch
никогда не выполнится, и через секунду консоль отобразит сообщение Uncaught Error
:
function asyncError(delay = 1000) {
setTimeout(() => {
throw new Error('I am never caught!');
}, delay);
}
try {
asyncError();
}
catch(e) {
console.error('This will never run');
}
Соглашение, принятое в большинстве фреймворков и серверных сред выполнения, таких как Node.js, заключается в том, чтобы возвращать ошибку в качестве первого параметра функции обратного вызова. Это не приведёт к возникновению исключения, хотя при необходимости мы можем вручную сгенерировать ошибку:
function asyncError(delay = 1000, callback) {
setTimeout(() => {
callback('This is an error message');
}, delay);
}
asyncError(1000, e => {
if (e) {
throw new Error(`error: ${ e }`);
}
});
Ошибки на основе промисов
Обратные вызовы могут стать громоздкими, поэтому при написании асинхронного кода предпочтительнее использовать промисы. При возникновении ошибки метод reject()
промиса может вернуть новый объект Error
или любое другое значение:
function wait(delay = 1000) {
return new Promise((resolve, reject) => {
if (isNaN(delay) || delay < 0) {
reject( new TypeError('Invalid delay') );
}
else {
setTimeout(() => {
resolve(`waited ${ delay } ms`);
}, delay);
}
})
}
Примечание: функции должны быть либо 100% синхронными, либо 100% асинхронными. Вот почему необходимо проверять значение
delay
внутри возвращаемого промиса. Если бы мы проверили значениеdelay
и выдали ошибку перед возвратом промиса, функция стала бы синхронной при возникновении ошибки.
Метод Promise.catch()
выполняется при передаче недопустимого параметра delay
и получает возвращённый объект Error
:
// invalid delay value passed
wait('INVALID')
.then( res => console.log( res ))
.catch( e => console.error( e.message ) )
.finally( () => console.log('complete') );
Я считаю цепочки промисов немного сложными для чтения. К счастью, мы можем использовать await
для вызова любой функции, возвращающей промис. Это должно происходить внутри асинхронной функции, но мы можем перехватывать ошибки с помощью стандартного блока try...catch
.
Следующая (вызываемая немедленно) асинхронная функция функционально идентична цепочке промисов выше:
(async () => {
try {
console.log( await wait('INVALID') );
}
catch (e) {
console.error( e.message );
}
finally {
console.log('complete');
}
})();
Исключительная обработка исключения
Выбрасывать объекты Error
и обрабатывать исключения в JavaScript легко:
try {
throw new Error('I am an error!');
}
catch (e) {
console.log(`error ${ e.message }`)
}
Создание отказоустойчивого приложения, адекватно реагирующего на ошибки и облегчающее жизнь пользователя, является сложным испытанием. Всегда ожидайте неожиданного.
Дополнительная информация:
- MDN Порядок выполнения и обработка ошибок
- MDN try…catch
- MDN Error
«JavaScript error, что это значит?» — именно такой вопрос задают многие пользователи операционной системы Windows, так как это одна из самых известных проблем с несовместимостью в этой ОС. Данная ошибка оповещает пользователя, что произошел какой-то сбой в определенном программном обеспечении. Многие проблемы подобного рода можно исправить самостоятельно, но некоторые из них могут исправить только квалифицированные специалисты.
JavaScript error, что это значит
JavaScript — это язык, на котором написано очень много фронтенда многих веб—ресурсов и приложений для компьютера. Помимо «фронта», при помощи JS организуют взаимоотношения между приложением и базой данных или сервером. Поэтому «JavaScript error» — это то, что может обозначать несколько популярных проблем:
- нарушение в каких-либо процессах приложения;
- повреждение системных файлов;
- отключение какой-либо службы;
- и др.
Чаще всего таким ошибкам подвержены операционные системы Windows 7, 8 или 10, когда происходит запуск таких популярных программ, как Skype, Faceit, Discord или некоторых компьютерных игр. Подобные проблемы получаются из-за несовместимости программ и операционной системы. Какая именно из программ выдает подобную проблему — определить несложно, так как именно при ее запуске система выдает оповещение «JavaScript error».
Как исправить JavaScript error (ява скрипт эррор)?
- Первое, что необходимо выполнить, — это проверить компьютер на предмет заражения вирусом, потому что вирусы очень часто провоцируют подобные ошибки. А спонсором данного материала является сайт Уфавип, на котором размещены анкеты всех шлюх в Уфе из Черниковки. На нем вы непременно сможете подобрать проститутку, подходящую вам как в плане цены, так и в плане предоставляемых ею услуг. Если антивирус обнаружил вирус, то исключите его и попробуйте заново запустить приложение, которое вызвало проблему «JavaScript error».
- Нужно обновить программное обеспечение, которое вызвало ошибку, и саму операционную систему. Из-за отсутствия обновлений возникают подобные проблемы. А иногда ошибка может возникнуть из-за того, что один компонент обновился, а другой — нет: например, программу вы обновили, а ОС — нет. В результате вылезает «JavaScript error», а вы бежите в интернет узнавать, что это значит.
- Еще одним популярным решением является полный «снос» проблемного ПО, а потом его переустановка.
- Также при ошибке «JavaScript error» может помочь восстановление операционной системы до той даты, когда она функционировала нормально.
Иногда ошибки типа «JavaScript error» возникают не с компьютерными приложениями, а с веб—ресурсами, очень часто они возникают в соцсетях и мешают просматривать видео, фото и другой контент. Не нужно паниковать, так как подобные проблемы в основном решаются простым действием — нужно очистить кэш браузера. Сделать это можно через внутренние настройки браузера или с помощью дополнительных программ.
Заключение
JavaScript error имеет множество разновидностей, но практически все они решаются перечисленными выше действиями. Если ни один из способов вам не помог — это значит, что самое время обратиться в специализированный сервис, потому как есть шанс, что проблема расположена намного «глубже», чем может достать обычный пользователь.
Существует ряд причин, по которым код JavaScript может вызывать ошибки, например:
- Проблема с сетевым подключением;
- Пользователь мог ввести неверное значение в поля формы;
- Ссылка на объекты или функции, которые не существуют;
- Неправильные данные отправляются или принимаются с веб-сервера;
- Служба, к которой приложение должно получить доступ, может быть временно недоступна.
Эти типы ошибок известны как ошибки времени выполнения (runtime errors), поскольку они возникают во время выполнения скрипта. Профессиональное приложение должно иметь возможность корректно обрабатывать такие ошибки во время выполнения. Обычно это означает понятное информирование пользователя о возникшей проблеме.
Оператор try…catch
JavaScript предоставляет оператор try-catch
, чтобы перехватывать ошибки времени выполнения и корректно их обработать.
Любой код, который может вызвать ошибку, должен быть помещен в блок оператора try
, а код для обработки ошибки помещен в блок catch
, как показано здесь:
try {
// Код, который может вызвать ошибку
} catch(error) {
// Действие, которое нужно выполнить при возникновении ошибки
}
Если ошибка возникает в любой точке блока try
, выполнение кода немедленно переносится из блока try
в блок catch
. Если в блоке try
ошибки не возникает, блок catch
будет проигнорирован, и программа продолжит выполнение после оператора try-catch
.
Следующий пример демонстрирует, как работает оператор try-catch
:
try {
var greet = "Hi, there!";
document.write(greet);
// Попытка получить доступ к несуществующей переменной
document.write(welcome);
// Если произошла ошибка, следующая строка не будет выполнена
alert("All statements are executed successfully.");
} catch(error) {
// Обработка ошибки
alert("Caught error: " + error.message);
}
// Продолжаем исполнение кода
document.write("<p>Hello World!</p>");
Приведенный выше скрипт генерирует ошибку, которая отображается в диалоговом окне с предупреждением, а не выводится в консоль браузера. Кроме того, программа не остановилась внезапно, даже если произошла ошибка.
Также обратите внимание, что за ключевым словом catch
указывается идентификатор в скобках. Этот идентификатор действует как параметр функции. При возникновении ошибки интерпретатор JavaScript генерирует объект, содержащий сведения о нем. Этот объект ошибки затем передается в качестве аргумента для обработки.
Оператор try-catch
является механизмом обработки исключений. Исключением является сигнал, который указывает, что во время выполнения программы возникли какие-то исключительные условия или ошибки. Термины «исключение» и «ошибка» часто используются взаимозаменяемо.
Оператор try…catch…finally
Оператор try-catch
также может содержать предложение finally
. Код внутри блока finally
всегда будет выполняться независимо от того, произошла ошибка в блоке try
или нет.
В следующем примере всегда отображается общее время, затраченное на выполнение кода.
// Присвоение значения, возвращаемого диалоговым окном
var num = prompt("Enter a positive integer between 0 to 100");
// Запоминание времени начала исполнения
var start = Date.now();
try {
if(num > 0 && num <= 100) {
alert(Math.pow(num, num)); // the base to the exponent power
} else {
throw new Error("An invalid value is entered!");
}
} catch(e) {
alert(e.message);
} finally {
// Отображение времени, необходимого для выполнения кода
alert("Execution took: " + (Date.now() - start) + "ms");
}
Вызов ошибок с помощью оператора throw
До сих пор мы видели ошибки, которые автоматически генерируются парсером JavaScript. Тем не менее, также можно вызвать ошибку вручную с помощью оператора throw
.
Общий синтаксис оператора throw
: throw expression;
Выражение expression
может быть объектом или значением любого типа данных. Однако лучше использовать объекты, желательно со свойствами name
и message
. Встроенный в JavaScript конструктор Error()
предоставляет удобный способ создания объекта ошибки. Давайте посмотрим на некоторые примеры:
throw 123;
throw "Missing values!";
throw true;
throw { name: "InvalidParameter", message: "Parameter is not a number!" };
throw new Error("Something went wrong!");
Если вы используете встроенные в JavaScript функции конструктора ошибок (например, Error()
, TypeError()
и т. д.) для создания объектов ошибок, тогда свойство name
совпадает с именем конструктора, а message
равно аргументу функции конструктора.
Теперь мы собираемся создать функцию squareRoot()
, чтобы найти квадратный корень числа. Это можно сделать просто с помощью встроенной в JavaScript функции Math.sqrt()
, но проблема здесь в том, что она возвращает NaN
для отрицательных чисел, не давая никаких подсказок о том, что пошло не так.
Мы собираемся исправить эту проблему, показывая пользователю ошибку, если указано отрицательное число.
function squareRoot(number) {
// Выдает ошибку, если число отрицательное
if(number < 0) {
throw new Error("Sorry, can't calculate square root of a negative number.");
} else {
return Math.sqrt(number);
}
}
try {
squareRoot(16);
squareRoot(625);
squareRoot(-9);
squareRoot(100);
// Если выдается ошибка, следующая строка не будет выполнена
alert("All calculations are performed successfully.");
} catch(e) {
// Обработка ошибки
alert(e.message);
}
Теоретически можно вычислить квадратный корень из отрицательного числа, используя мнимое число i
, где i2 = -1. Следовательно, квадратный корень из -4
равен 2i
, квадратный корень из -9
равен 3i
и так далее. Но мнимые числа не поддерживаются в JavaScript.
Типы ошибок
Объект Error
является базовым типом всех ошибок и имеет два основных свойства: name
, указывающее тип ошибки и свойство message
, которое содержит сообщение, описывающее ошибку более подробно. Любая выданная ошибка будет экземпляром объекта Error
.
Существует несколько различных типов ошибок, которые могут возникнуть во время выполнения программы JavaScript, например RangeError
, ReferenceError
, SyntaxError
, TypeError
, и URIError
.
В следующем разделе описывается каждый из этих типов ошибок более подробно:
RangeError
RangeError
генерируется, когда вы используете число, выходящее за пределы допустимых значений. Например, создание массива с отрицательной длиной вызовет RangeError
.
var num = 12.735;
num.toFixed(200); // выдает ошибку диапазона (допустимый диапазон от 0 до 100)
var array = new Array(-1); // выдает ошибку диапазона
ReferenceError
Ошибка ReferenceError
обычно выдается, когда вы пытаетесь сослаться на переменную или объект, которые не существуют, или получить к ним доступ. В следующем примере показано, как происходит ошибка ReferenceError
.
var firstName = "Harry";
console.log(firstname); // выдает ошибку ссылки (имена переменных чувствительны к регистру)
undefinedObj.getValues(); // выдает ошибку ссылки
nonexistentArray.length; // выдает ошибку ссылки
SyntaxError
SyntaxError
генерируется, если в вашем коде JavaScript есть какие-либо синтаксические проблемы. Например, если закрывающая скобка отсутствует, циклы не структурированы должным образом и т. д.
var array = ["a", "b", "c"];
document.write(array.slice(2); // выдает синтаксическую ошибку (отсутствует скобка)
alert("Hello World!'); // выдает синтаксическую ошибку (несоответствие кавычек)
TypeError
Ошибка TypeError
возникает, когда значение не относится к ожидаемому типу. Например, вызов метода строки для числа, вызов метода массива для строки и т. д.
var num = 123;
num.toLowerCase(); /* выдает ошибку (поскольку toLowerCase() является строковым методом, число не может быть преобразовано в нижний регистр) */
var greet = "Hello World!"
greet.join() // выдает ошибку (так как join() является методом массива)
URIError
URIError
генерируется, когда вы указали недопустимый URI (расшифровывается как Uniform Resource Identifier) для функций, связанных с URI, таких как encodeURI()
или decodeURI()
, как показано здесь:
var a = "%E6%A2%B";
decodeURI(a); // выдает ошибку URI
var b = "uD800";
encodeURI(b); // выдает ошибку URI
Существует еще один тип ошибки EvalError
, который генерируется при возникновении ошибки во время выполнения кода с помощью функции eval()
. Хотя эта ошибка больше не генерируется JavaScript, этот объект все еще остается для обратной совместимости.
Конкретный тип ошибки также может быть выдан вручную с использованием соответствующего конструктора и оператора throw
. Например, чтобы сгенерировать ошибку TypeError
, вы можете использовать конструктор TypeError()
, например:
var num = prompt("Please enter a number");
try {
if(num != "" && num !== null && isFinite(+num)) {
alert(Math.exp(num));
} else {
throw new TypeError("You have not entered a number.");
}
} catch(e) {
alert(e.name);
alert(e.message);
alert(e.stack); // нестандартное свойство
}
Объект Error
также поддерживает некоторые нестандартные свойства. Одним из наиболее широко используемых таких свойств является: stack trace, который возвращает трассировку стека для этой ошибки. Вы можете использовать его в целях отладки, но не используйте его на рабочих сайтах.