I’m using JSON.parse
on a response that sometimes contains a 404 response. In the cases where it returns 404, is there a way to catch an exception and then execute some other code?
data = JSON.parse(response, function (key, value) {
var type;
if (value && typeof value === 'object') {
type = value.type;
if (typeof type === 'string' && typeof window[type] === 'function') {
return new(window[type])(value);
}
}
return value;
});
asked Dec 17, 2010 at 1:52
3
i post something into an iframe then read back the contents of the iframe with json parse…so sometimes it’s not a json string
Try this:
if (response) {
let a;
try {
a = JSON.parse(response);
} catch (e) {
return console.error(e); // error in the above string (in this case, yes)!
}
// if no error, we can now keep using "a"
}
answered Dec 17, 2010 at 3:08
UltraInstinctUltraInstinct
43.1k12 gold badges80 silver badges102 bronze badges
4
We can check error & 404 statusCode, and use try {} catch (err) {}
.
You can try this :
const req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.status == 404) {
console.log("404");
return false;
}
if (!(req.readyState == 4 && req.status == 200))
return false;
const json = (function(raw) {
try {
return JSON.parse(raw);
} catch (err) {
return false;
}
})(req.responseText);
if (!json)
return false;
document.body.innerHTML = "Your city : " + json.city + "<br>Your isp : " + json.org;
};
req.open("GET", "https://ipapi.co/json/", true);
req.send();
Read more :
- Catch a 404 error for XHR
answered Jul 13, 2017 at 13:45
user2226755user2226755
12.3k5 gold badges50 silver badges73 bronze badges
I am fairly new to Javascript. But this is what I understood:
JSON.parse()
returns SyntaxError
exceptions when invalid JSON is provided as its first parameter. So. It would be better to catch that exception as such like as follows:
try {
let sData = `
{
"id": "1",
"name": "UbuntuGod",
}
`;
console.log(JSON.parse(sData));
} catch (objError) {
if (objError instanceof SyntaxError) {
console.error(objError.name);
} else {
console.error(objError.message);
}
}
The reason why I made the words «first parameter» bold is that JSON.parse()
takes a reviver function as its second parameter.
answered Jul 21, 2018 at 3:15
ubuntugodubuntugod
5927 silver badges16 bronze badges
3
if you are looking for a generalized function for this, give this a shot.
const parseJSON = (inputString, fallback) => {
if (inputString) {
try {
return JSON.parse(inputString);
} catch (e) {
return fallback;
}
}
};
answered Jan 21, 2022 at 7:56
I recommend you to use this one as a ES6 best practice. Using Error
object
try {
myResponse = JSON.parse(response);
} catch (e) {
throw new Error('Error occured: ', e);
}
Above answer also helpful,
answered Feb 28, 2022 at 8:52
1
You can try this:
Promise.resolve(JSON.parse(response)).then(json => {
response = json ;
}).catch(err => {
response = response
});
dereli
1,80415 silver badges23 bronze badges
answered Mar 5, 2019 at 5:36
This promise will not resolve if the argument of JSON.parse() can not be parsed into a JSON object.
Promise.resolve(JSON.parse('{"key":"value"}')).then(json => {
console.log(json);
}).catch(err => {
console.log(err);
});
answered Oct 5, 2017 at 7:32
2
The best way to catch invalid JSON parsing errors is to put the calls to JSON.parse()
to a try/catch
block.
You really do not have any other option — the built-in implementation throws an exception on invalid JSON data and the only way to prevent that exception from halting your application is to catch it. Even using a 3rd party library will not avoid that — they must do a try/catch
on a JSON.parse()
call somewhere.
The only alternative is to implement your own JSON parsing algorithm that could be more forgiving on invalid data structures, but that feels like digging a 1 cubic metre hole with a small nuke.
Note about performance
The v8 JavaScript engine used by Node.js cannot optimise functions which contain a try/catch
block.
Update: v8 4.5 and above can optimise try/catch. For older releases, see below.
A simple workaround is to put the safe-parsing logic into a separate function so that the main function can still be optimised:
function safelyParseJSON (json) {
// This function cannot be optimised, it's best to
// keep it small!
var parsed
try {
parsed = JSON.parse(json)
} catch (e) {
// Oh well, but whatever...
}
return parsed // Could be undefined!
}
function doAlotOfStuff () {
// ... stuff stuff stuff
var json = safelyParseJSON(data)
// Tadaa, I just got rid of an optimisation killer!
}
If the JSON parsing is done sporadically, this might not have a noticeable effect on performance, but if used improperly in usage-heavy function it could lead to dramatic increase in response times.
Note about try/catch being blocking
It should be noted that every.single.statement of JavaScript code in Node.js is executed only one-at-a-time, no matter if it’s called from the main function or from a callback or from a different module or whatever. As such, every single statement will block the process. This is not necessarily a bad thing — a well designed application will spend most of its time waiting for an external resource (database response, HTTP communication, filesystem operations etc.). It is therefore of great importance that frequently executed JavaScript code can be optimised by the v8 engine so it takes as little time as possible in this blocked state — see the note about performance.
JSON ( JavaScript Object Notation), is widely used format for asynchronous communication between webpage or mobile application to back-end servers. Due to increasing trend in Single Page Application or Mobile Application, popularity of the JSON is extreme.
Why do we get JSON parse error?
Parsing JSON is a very common task in JavaScript. JSON.parse() is a built-in method in JavaScript which is used to parse a JSON string and convert it into a JavaScript object. If the JSON string is invalid, it will throw a SyntaxError.
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);
console.log(obj.count);
// expected output: 42
console.log(obj.result);
// expected output: true
How to handle JSON parse error?
There are many ways to handle JSON parse error. In this post, I will show you how to handle JSON parse error in JavaScript.
1. Using try-catch block
The most common way to handle JSON parse error is using try-catch block. If the JSON string is valid, it will return a JavaScript object. If the JSON string is invalid, it will throw a SyntaxError.
try {
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);
console.log(obj.count);
// expected output: 42
console.log(obj.result);
// expected output: true
} catch (e) {
console.log(e);
// expected output: SyntaxError: Unexpected token o in JSON at position 1
}
2. Using if-else block
Another way to handle JSON parse error is using if-else block.
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);
if (obj instanceof SyntaxError) {
console.log(obj);
// expected output: SyntaxError: Unexpected token o in JSON at position 1
} else {
console.log(obj.count);
// expected output: 42
console.log(obj.result);
// expected output: true
}
3. Using try-catch block with JSON.parse()
The third way to handle JSON parse error is using try-catch block with JSON.parse().
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json, (key, value) => {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
});
console.log(obj.count);
// expected output: 42
console.log(obj.result);
// expected output: true
4. Using try-catch block with JSON.parse() and JSON.stringify()
The fourth way to handle JSON parse error is using try-catch block with JSON.parse() and JSON.stringify(). If the JSON string is valid, it will return a JavaScript object. If the JSON string is invalid, it will return a SyntaxError.
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json, (key, value) => {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
});
const str = JSON.stringify(obj);
console.log(str);
// expected output: {"result":true,"count":42}
Обработка ошибок, «try..catch»
Неважно, насколько мы хороши в программировании, иногда наши скрипты содержат ошибки. Они могут возникать из-за наших промахов, неожиданного ввода пользователя, неправильного ответа сервера и по тысяче других причин.
Обычно скрипт в случае ошибки «падает» (сразу же останавливается), с выводом ошибки в консоль.
Но есть синтаксическая конструкция try..catch
, которая позволяет «ловить» ошибки и вместо падения делать что-то более осмысленное.
Синтаксис «try..catch»
Конструкция try..catch
состоит из двух основных блоков: try
, и затем catch
:
try { // код... } catch (err) { // обработка ошибки }
Работает она так:
- Сначала выполняется код внутри блока
try {...}
. - Если в нём нет ошибок, то блок
catch(err)
игнорируется: выполнение доходит до концаtry
и потом далее, полностью пропускаяcatch
. - Если же в нём возникает ошибка, то выполнение
try
прерывается, и поток управления переходит в началоcatch(err)
. Переменнаяerr
(можно использовать любое имя) содержит объект ошибки с подробной информацией о произошедшем.
Таким образом, при ошибке в блоке try {…}
скрипт не «падает», и мы получаем возможность обработать ошибку внутри catch
.
Давайте рассмотрим примеры.
-
Пример без ошибок: выведет
alert
(1)
и(2)
:try { alert('Начало блока try'); // *!*(1) <--*/!* // ...код без ошибок alert('Конец блока try'); // *!*(2) <--*/!* } catch(err) { alert('Catch игнорируется, так как нет ошибок'); // (3) }
-
Пример с ошибками: выведет
(1)
и(3)
:try { alert('Начало блока try'); // *!*(1) <--*/!* *!* lalala; // ошибка, переменная не определена! */!* alert('Конец блока try (никогда не выполнится)'); // (2) } catch(err) { alert(`Возникла ошибка!`); // *!*(3) <--*/!* }
««warn header=»try..catch
работает только для ошибок, возникающих во время выполнения кода»
Чтобы `try..catch` работал, код должен быть выполнимым. Другими словами, это должен быть корректный JavaScript-код.
Он не сработает, если код синтаксически неверен, например, содержит несовпадающее количество фигурных скобок:
try { {{{{{{{{{{{{ } catch(e) { alert("Движок не может понять этот код, он некорректен"); }
JavaScript-движок сначала читает код, а затем исполняет его. Ошибки, которые возникают во время фазы чтения, называются ошибками парсинга. Их нельзя обработать (изнутри этого кода), потому что движок не понимает код.
Таким образом, try..catch
может обрабатывать только ошибки, которые возникают в корректном коде. Такие ошибки называют «ошибками во время выполнения», а иногда «исключениями».
````warn header="`try..catch` работает синхронно"
Исключение, которое произойдёт в коде, запланированном "на будущее", например в `setTimeout`, `try..catch` не поймает:
```js run
try {
setTimeout(function() {
noSuchVariable; // скрипт упадёт тут
}, 1000);
} catch (e) {
alert( "не сработает" );
}
```
Это потому, что функция выполняется позже, когда движок уже покинул конструкцию `try..catch`.
Чтобы поймать исключение внутри запланированной функции, `try..catch` должен находиться внутри самой этой функции:
```js run
setTimeout(function() {
try {
noSuchVariable; // try..catch обрабатывает ошибку!
} catch {
alert( "ошибка поймана!" );
}
}, 1000);
```
Объект ошибки
Когда возникает ошибка, JavaScript генерирует объект, содержащий её детали. Затем этот объект передаётся как аргумент в блок catch
:
try { // ... } catch(err) { // <-- объект ошибки, можно использовать другое название вместо err // ... }
Для всех встроенных ошибок этот объект имеет два основных свойства:
name
: Имя ошибки. Например, для неопределённой переменной это "ReferenceError"
.
message
: Текстовое сообщение о деталях ошибки.
В большинстве окружений доступны и другие, нестандартные свойства. Одно из самых широко используемых и поддерживаемых — это:
stack
: Текущий стек вызова: строка, содержащая информацию о последовательности вложенных вызовов, которые привели к ошибке. Используется в целях отладки.
Например:
try { *!* lalala; // ошибка, переменная не определена! */!* } catch(err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at (...стек вызовов) // Можем также просто вывести ошибку целиком // Ошибка приводится к строке вида "name: message" alert(err); // ReferenceError: lalala is not defined }
Блок «catch» без переменной
[recent browser=new]
Если нам не нужны детали ошибки, в catch
можно её пропустить:
try { // ... } catch { // <-- без (err) // ... }
Использование «try..catch»
Давайте рассмотрим реальные случаи использования try..catch
.
Как мы уже знаем, JavaScript поддерживает метод JSON.parse(str) для чтения JSON.
Обычно он используется для декодирования данных, полученных по сети, от сервера или из другого источника.
Мы получаем их и вызываем JSON.parse
вот так:
let json = '{"name":"John", "age": 30}'; // данные с сервера *!* let user = JSON.parse(json); // преобразовали текстовое представление в JS-объект */!* // теперь user - объект со свойствами из строки alert( user.name ); // John alert( user.age ); // 30
Вы можете найти более детальную информацию о JSON в главе info:json.
Если json
некорректен, JSON.parse
генерирует ошибку, то есть скрипт «падает».
Устроит ли нас такое поведение? Конечно нет!
Получается, что если вдруг что-то не так с данными, то посетитель никогда (если, конечно, не откроет консоль) об этом не узнает. А люди очень не любят, когда что-то «просто падает» без всякого сообщения об ошибке.
Давайте используем try..catch
для обработки ошибки:
let json = "{ некорректный JSON }"; try { *!* let user = JSON.parse(json); // <-- тут возникает ошибка... */!* alert( user.name ); // не сработает } catch (e) { *!* // ...выполнение прыгает сюда alert( "Извините, в данных ошибка, мы попробуем получить их ещё раз." ); alert( e.name ); alert( e.message ); */!* }
Здесь мы используем блок catch
только для вывода сообщения, но мы также можем сделать гораздо больше: отправить новый сетевой запрос, предложить посетителю альтернативный способ, отослать информацию об ошибке на сервер для логирования, … Всё лучше, чем просто «падение».
Генерация собственных ошибок
Что если json
синтаксически корректен, но не содержит необходимого свойства name
?
Например, так:
let json = '{ "age": 30 }'; // данные неполны try { let user = JSON.parse(json); // <-- выполнится без ошибок *!* alert( user.name ); // нет свойства name! */!* } catch (e) { alert( "не выполнится" ); }
Здесь JSON.parse
выполнится без ошибок, но на самом деле отсутствие свойства name
для нас ошибка.
Для того, чтобы унифицировать обработку ошибок, мы воспользуемся оператором throw
.
Оператор «throw»
Оператор throw
генерирует ошибку.
Синтаксис:
Технически в качестве объекта ошибки можно передать что угодно. Это может быть даже примитив, число или строка, но всё же лучше, чтобы это был объект, желательно со свойствами name
и message
(для совместимости со встроенными ошибками).
В JavaScript есть множество встроенных конструкторов для стандартных ошибок: Error
, SyntaxError
, ReferenceError
, TypeError
и другие. Можно использовать и их для создания объектов ошибки.
Их синтаксис:
let error = new Error(message); // или let error = new SyntaxError(message); let error = new ReferenceError(message); // ...
Для встроенных ошибок (не для любых объектов, только для ошибок), свойство name
— это в точности имя конструктора. А свойство message
берётся из аргумента.
Например:
let error = new Error(" Ого, ошибка! o_O"); alert(error.name); // Error alert(error.message); // Ого, ошибка! o_O
Давайте посмотрим, какую ошибку генерирует JSON.parse
:
try { JSON.parse("{ bad json o_O }"); } catch(e) { *!* alert(e.name); // SyntaxError */!* alert(e.message); // Unexpected token b in JSON at position 2 }
Как мы видим, это SyntaxError
.
В нашем случае отсутствие свойства name
— это ошибка, ведь пользователи должны иметь имена.
Сгенерируем её:
let json = '{ "age": 30 }'; // данные неполны try { let user = JSON.parse(json); // <-- выполнится без ошибок if (!user.name) { *!* throw new SyntaxError("Данные неполны: нет имени"); // (*) */!* } alert( user.name ); } catch(e) { alert( "JSON Error: " + e.message ); // JSON Error: Данные неполны: нет имени }
В строке (*)
оператор throw
генерирует ошибку SyntaxError
с сообщением message
. Точно такого же вида, как генерирует сам JavaScript. Выполнение блока try
немедленно останавливается, и поток управления прыгает в catch
.
Теперь блок catch
становится единственным местом для обработки всех ошибок: и для JSON.parse
и для других случаев.
Проброс исключения
В примере выше мы использовали try..catch
для обработки некорректных данных. А что, если в блоке try {...}
возникнет другая неожиданная ошибка? Например, программная (неопределённая переменная) или какая-то ещё, а не ошибка, связанная с некорректными данными.
Пример:
let json = '{ "age": 30 }'; // данные неполны try { user = JSON.parse(json); // <-- забыл добавить "let" перед user // ... } catch(err) { alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined // (не JSON ошибка на самом деле) }
Конечно, возможно все! Программисты совершают ошибки. Даже в утилитах с открытым исходным кодом, используемых миллионами людей на протяжении десятилетий — вдруг может быть обнаружена ошибка, которая приводит к ужасным взломам.
В нашем случае try..catch
предназначен для выявления ошибок, связанных с некорректными данными. Но по своей природе catch
получает все свои ошибки из try
. Здесь он получает неожиданную ошибку, но всё также показывает то же самое сообщение "JSON Error"
. Это неправильно и затрудняет отладку кода.
К счастью, мы можем выяснить, какую ошибку мы получили, например, по её свойству name
:
try { user = { /*...*/ }; } catch(e) { *!* alert(e.name); // "ReferenceError" из-за неопределённой переменной */!* }
Есть простое правило:
Блок catch
должен обрабатывать только те ошибки, которые ему известны, и «пробрасывать» все остальные.
Техника «проброс исключения» выглядит так:
- Блок
catch
получает все ошибки. - В блоке
catch(err) {...}
мы анализируем объект ошибкиerr
. - Если мы не знаем как её обработать, тогда делаем
throw err
.
В коде ниже мы используем проброс исключения, catch
обрабатывает только SyntaxError
:
let json = '{ "age": 30 }'; // данные неполны try { let user = JSON.parse(json); if (!user.name) { throw new SyntaxError("Данные неполны: нет имени"); } *!* blabla(); // неожиданная ошибка */!* alert( user.name ); } catch(e) { *!* if (e.name == "SyntaxError") { alert( "JSON Error: " + e.message ); } else { throw e; // проброс (*) } */!* }
Ошибка в строке (*)
из блока catch
«выпадает наружу» и может быть поймана другой внешней конструкцией try..catch
(если есть), или «убьёт» скрипт.
Таким образом, блок catch
фактически обрабатывает только те ошибки, с которыми он знает, как справляться, и пропускает остальные.
Пример ниже демонстрирует, как такие ошибки могут быть пойманы с помощью ещё одного уровня try..catch
:
function readData() { let json = '{ "age": 30 }'; try { // ... *!* blabla(); // ошибка! */!* } catch (e) { // ... if (e.name != 'SyntaxError') { *!* throw e; // проброс исключения (не знаю как это обработать) */!* } } } try { readData(); } catch (e) { *!* alert( "Внешний catch поймал: " + e ); // поймал! */!* }
Здесь readData
знает только, как обработать SyntaxError
, тогда как внешний блок try..catch
знает, как обработать всё.
try..catch..finally
Подождите, это ещё не всё.
Конструкция try..catch
может содержать ещё одну секцию: finally
.
Если секция есть, то она выполняется в любом случае:
- после
try
, если не было ошибок, - после
catch
, если ошибки были.
Расширенный синтаксис выглядит следующим образом:
*!*try*/!* { ... пробуем выполнить код... } *!*catch*/!*(e) { ... обрабатываем ошибки ... } *!*finally*/!* { ... выполняем всегда ... }
Попробуйте запустить такой код:
try { alert( 'try' ); if (confirm('Сгенерировать ошибку?')) BAD_CODE(); } catch (e) { alert( 'catch' ); } finally { alert( 'finally' ); }
У кода есть два пути выполнения:
- Если вы ответите на вопрос «Сгенерировать ошибку?» утвердительно, то
try -> catch -> finally
. - Если ответите отрицательно, то
try -> finally
.
Секцию finally
часто используют, когда мы начали что-то делать и хотим завершить это вне зависимости от того, будет ошибка или нет.
Например, мы хотим измерить время, которое занимает функция чисел Фибоначчи fib(n)
. Естественно, мы можем начать измерения до того, как функция начнёт выполняться и закончить после. Но что делать, если при вызове функции возникла ошибка? В частности, реализация fib(n)
в коде ниже возвращает ошибку для отрицательных и для нецелых чисел.
Секция finally
отлично подходит для завершения измерений несмотря ни на что.
Здесь finally
гарантирует, что время будет измерено корректно в обеих ситуациях — и в случае успешного завершения fib
и в случае ошибки:
let num = +prompt("Введите положительное целое число?", 35) let diff, result; function fib(n) { if (n < 0 || Math.trunc(n) != n) { throw new Error("Должно быть целое неотрицательное число"); } return n <= 1 ? n : fib(n - 1) + fib(n - 2); } let start = Date.now(); try { result = fib(num); } catch (e) { result = 0; *!* } finally { diff = Date.now() - start; } */!* alert(result || "возникла ошибка"); alert( `Выполнение заняло ${diff}ms` );
Вы можете это проверить, запустив этот код и введя 35
в prompt
— код завершится нормально, finally
выполнится после try
. А затем введите -1
— незамедлительно произойдёт ошибка, выполнение займёт 0ms
. Оба измерения выполняются корректно.
Другими словами, неважно как завершилась функция: через return
или throw
. Секция finally
срабатывает в обоих случаях.
«`smart header=»Переменные внутри try..catch..finally
локальны»
Обратите внимание, что переменные `result` и `diff` в коде выше объявлены до `try..catch`.
Если переменную объявить в блоке, например, в try
, то она не будет доступна после него.
````smart header="`finally` и `return`"
Блок `finally` срабатывает при *любом* выходе из `try..catch`, в том числе и `return`.
В примере ниже из `try` происходит `return`, но `finally` получает управление до того, как контроль возвращается во внешний код.
```js run
function func() {
try {
*!*
return 1;
*/!*
} catch (e) {
/* ... */
} finally {
*!*
alert( 'finally' );
*/!*
}
}
alert( func() ); // сначала срабатывает alert из finally, а затем этот код
````smart header="`try..finally`"
Конструкция `try..finally` без секции `catch` также полезна. Мы применяем её, когда не хотим здесь обрабатывать ошибки (пусть выпадут), но хотим быть уверены, что начатые процессы завершились.
```js
function func() {
// начать делать что-то, что требует завершения (например, измерения)
try {
// ...
} finally {
// завершить это, даже если все упадёт
}
}
```
В приведённом выше коде ошибка всегда выпадает наружу, потому что тут нет блока `catch`. Но `finally` отрабатывает до того, как поток управления выйдет из функции.
Глобальный catch
Информация из данной секции не является частью языка JavaScript.
Давайте представим, что произошла фатальная ошибка (программная или что-то ещё ужасное) снаружи try..catch
, и скрипт упал.
Существует ли способ отреагировать на такие ситуации? Мы можем захотеть залогировать ошибку, показать что-то пользователю (обычно они не видят сообщение об ошибке) и т.д.
Такого способа нет в спецификации, но обычно окружения предоставляют его, потому что это весьма полезно. Например, в Node.js для этого есть process.on("uncaughtException")
. А в браузере мы можем присвоить функцию специальному свойству window.onerror, которая будет вызвана в случае необработанной ошибки.
Синтаксис:
window.onerror = function(message, url, line, col, error) { // ... };
message
: Сообщение об ошибке.
url
: URL скрипта, в котором произошла ошибка.
line
, col
: Номера строки и столбца, в которых произошла ошибка.
error
: Объект ошибки.
Пример:
<script> *!* window.onerror = function(message, url, line, col, error) { alert(`${message}n В ${line}:${col} на ${url}`); }; */!* function readData() { badFunc(); // Ой, что-то пошло не так! } readData(); </script>
Роль глобального обработчика window.onerror
обычно заключается не в восстановлении выполнения скрипта — это скорее всего невозможно в случае программной ошибки, а в отправке сообщения об ошибке разработчикам.
Существуют также веб-сервисы, которые предоставляют логирование ошибок для таких случаев, такие как https://errorception.com или http://www.muscula.com.
Они работают так:
- Мы регистрируемся в сервисе и получаем небольшой JS-скрипт (или URL скрипта) от них для вставки на страницы.
- Этот JS-скрипт ставит свою функцию
window.onerror
. - Когда возникает ошибка, она выполняется и отправляет сетевой запрос с информацией о ней в сервис.
- Мы можем войти в веб-интерфейс сервиса и увидеть ошибки.
Итого
Конструкция try..catch
позволяет обрабатывать ошибки во время исполнения кода. Она позволяет запустить код и перехватить ошибки, которые могут в нём возникнуть.
Синтаксис:
try { // исполняем код } catch(err) { // если случилась ошибка, прыгаем сюда // err - это объект ошибки } finally { // выполняется всегда после try/catch }
Секций catch
или finally
может не быть, то есть более короткие конструкции try..catch
и try..finally
также корректны.
Объекты ошибок содержат следующие свойства:
message
— понятное человеку сообщение.name
— строка с именем ошибки (имя конструктора ошибки).stack
(нестандартное, но хорошо поддерживается) — стек на момент ошибки.
Если объект ошибки не нужен, мы можем пропустить его, используя catch {
вместо catch(err) {
.
Мы можем также генерировать собственные ошибки, используя оператор throw
. Аргументом throw
может быть что угодно, но обычно это объект ошибки, наследуемый от встроенного класса Error
. Подробнее о расширении ошибок см. в следующей главе.
Проброс исключения — это очень важный приём обработки ошибок: блок catch
обычно ожидает и знает, как обработать определённый тип ошибок, поэтому он должен пробрасывать дальше ошибки, о которых он не знает.
Даже если у нас нет try..catch
, большинство сред позволяют настроить «глобальный» обработчик ошибок, чтобы ловить ошибки, которые «выпадают наружу». В браузере это window.onerror
.
Ошибки есть в каждом коде. Мы перевели гайд разработчика Айо Исайя, в котором он рассказывает о системе ошибок и о том, как их устранять.
Раз вы читаете эту статью, вы, конечно, знакомы с концепцией ошибок в программировании. Это ошибки в коде, они же баги, которые приводят к сбою или неожиданному поведению программы. В отличие от некоторых языков, таких как Go и Rust, где вы вынуждены взаимодействовать с потенциальными ошибками на каждом этапе пути, в JavaScript и Node.js можно обойтись без согласованной стратегии обработки ошибок.
Однако именно такая стратегия делает жизнь проще. Цель статьи — познакомить вас с этими шаблонами для создания, доставки и обработки потенциальных ошибок. Шаблоны помогут обнаружить и обработать потенциальные ошибки в коде до развёртывания.
Что такое ошибки в Node.js
Ошибка в Node.js — это любой экземпляр объекта Error. Общие примеры включают встроенные классы ошибок: ReferenceError
, RangeError
, TypeError
, URIError
, EvalError
и SyntaxError
. Пользовательские ошибки также можно создать путём расширения базового объекта Error, встроенного класса ошибки или другой настраиваемой ошибки. При создании ошибок таким путём нужно передать строку сообщения, описывающую ошибку. К сообщению можно получить доступ через свойство message
объекта. Объект Error
также содержит свойства name
и stack
, которые указывают имя ошибки и точку в коде, в которой объект создаётся.
const userError = new TypeError("Something happened!");
console.log(userError.name); // TypeError
console.log(userError.message); // Something happened!
console.log(userError.stack);
/*TypeError: Something happened!
at Object.<anonymous> (/home/ayo/dev/demo/main.js:2:19)
<truncated for brevity>
at node:internal/main/run_main_module:17:47 */
Функции объекта Error можно передать или вернуть из функции. Если бросить его с помощью throw
, объект Error станет исключением. Когда вы передаёте ошибку из функции, она переходит вверх по стеку, пока исключение не будет поймано. В противном случае uncaught exception
может обвалить всю работу.
Как обработать ошибку
Оптимальный способ обработки ошибок функции JavaScript зависит от того, выполняет ли эта функция синхронную или асинхронную операцию. Рассмотрим четыре общих шаблона, позволяющих обрабатывать ошибки функций в Node.js.
Исключения
Чаще всего ошибки функций обрабатывают путём генерации. В этом случае ошибка становится исключением, после чего её можно поймать где-нибудь в стеке с помощью блока try / catch
. Если у ошибки есть разрешение всплывать в стеке, не будучи перехваченной, она преобразуется в формат uncaughtException
, что приводит к преждевременному завершению работы приложения. Например, встроенный метод JSON.parse ()
выдаёт ошибку, если строковый аргумент не является допустимым объектом JSON.
function parseJSON(data) {
return JSON.parse(data);
}
try {
const result = parseJSON('A string');
} catch (err) {
console.log(err.message); // Unexpected token A in JSON at position 0
}
Для использования этого шаблона в функциях нужно добавить ключевое слово throw
перед экземпляром ошибки. Этот шаблон сообщения об ошибках и обработки идиоматичен для функций, выполняющих синхронные операции.
function square(num) {
if (typeof num !== 'number') {
throw new TypeError(`Expected number but got: ${typeof num}`);
}
return num * num;
}
try {
square('8');
} catch (err) {
console.log(err.message); // Expected number but got: string
}
Колбэк с первым аргументом-ошибкой
Из-за своей асинхронной природы Node.js интенсивно использует функции колбэка для обработки большей части ошибок. Колбэк (обратный вызов) передаётся в качестве аргумента другой функции и выполняется, когда последняя завершает свою работу.
Node.js использует колбэк с первым аргументом-ошибкой в большинстве асинхронных методов, чтобы гарантировать проверку ошибок до результатов операции. Колбэк обычно является последним аргументом функции, инициирующей асинхронную операцию, и вызывается один раз при возникновении ошибки или получении результата:
function (err, result) {}
Первый аргумент зарезервирован для объекта ошибки. Если ошибка возникает в ходе асинхронной операции, она доступна через аргумент err
при неопределённом результате. Однако, если ошибки не возникает, err
будет иметь значение null
или undefined
, а result
будет содержать ожидаемый результат операции. Этот шаблон работает, если прочитать содержимое файла с помощью встроенного метода fs.readFile ():
const fs = require('fs');
fs.readFile('/path/to/file.txt', (err, result) => {
if (err) {
console.error(err);
return;
}
// Log the file contents if no error
console.log(result);
});
Метод readFile ()
использует колбэк в качестве своего последнего аргумента, который, в свою очередь, соответствует подписи функции «первая ошибка». В этом сценарии result
включает в себя содержимое файла, который читается, если ошибки не возникает. В противном случае он определяется как undefined
, а аргумент err
заполняется объектом ошибки, содержащим информацию о проблеме: файл не найден или недостаточно полномочий.
Как правило, методы, использующие колбэк для обработки ошибок, не могут определить, насколько важна выявленная ошибка. Они возвращают ошибку пользователю для обработки. Важно контролировать поток содержимого колбэка, проверять функцию на наличие ошибки, прежде чем пытаться получить доступ к результату операции.
Чтобы использовать шаблон колбэка с первым аргументом-ошибкой в собственных асинхронных функциях, нужно принять функцию в качестве последнего аргумента и вызвать её:
function square(num, callback) {
if (typeof callback !== 'function') {
throw new TypeError(`Callback must be a function. Got: ${typeof callback}`);
}
// simulate async operation
setTimeout(() => {
if (typeof num !== 'number') {
// if an error occurs, it is passed as the first argument to the callback
callback(new TypeError(`Expected number but got: ${typeof num}`));
return;
}
const result = num * num;
// callback is invoked after the operation completes with the result
callback(null, result);
}, 100);
}
Любой вызывающий функцию square
должен пройти через колбэк, чтобы получить доступ к нужному результату или ошибке.
Не нужно непосредственно обрабатывать ошибку в функции колбэка. Её можно распространить вверх по стеку, передав на другой колбэк. Но сначала убедитесь, что вы не генерируете исключение внутри функции. Асинхронное исключение невозможно отследить, потому что окружающий блок try / catch
завершается до выполнения колбэка. Следовательно, исключение будет распространяться на вершину стека, что приведёт к завершению работы приложения. Исключение — когда обработчик зарегистрирован для process.on ('uncaughtException')
.
try {
square('8', (err, result) => {
if (err) {
throw err; // not recommended
}
console.log(result);
});
} catch (err) {
// This won't work
console.error("Caught error: ", err);
}
Отклонение обещаний
Обещания в JavaScript — это актуальный способ выполнения асинхронных операций в Node.js. Они предпочтительнее колбэков из-за лучшего потока, который соответствует современным способам анализа программ, особенно с шаблоном async / await
. Любой API-интерфейс Node.js, использующий колбэки с ошибкой для асинхронной обработки ошибок, может быть преобразован в обещания с помощью встроенного метода util.promisify ()
. Например, заставить метод fs.readFile ()
использовать обещания можно так:
const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
Переменная readFile
— это версия fs.readFile ()
с обещаниями, в которой отклонения обещаний используются для сообщения об ошибках. Эти ошибки можно отследить, связав метод catch
:
readFile('/path/to/file.txt')
.then((result) => console.log(result))
.catch((err) => console.error(err));
Также можно использовать обещанные API в функциях async
. Так выглядит основной способ использования обещаний в современном JavaScript: в нём код читается как синхронный, и для обработки ошибок применяют знакомый механизм try / catch
. Перед асинхронным запуском важно использовать await
, чтобы обещание было выполнено или отклонено до того, как функция возобновит выполнение. При отклонении обещания выражение await
выбрасывает отклонённое значение, которое впоследствии попадает в окружающий блок catch
.
(async function callReadFile() {
try {
const result = await readFile('/path/to/file.txt');
console.log(result);
} catch (err) {
console.error(err);
}
})();
Обещанияможно использовать в асинхронных функциях, возвращая обещание из функции и помещая код функции в обратный вызов обещания. Если есть ошибка, её стоит отклонить (reject) с помощью объекта Error. В противном случае можно разрешить (resolve) обещание с результатом, чтобы оно было доступно в цепочке метода .then
или напрямую как значение функции async
при использовании async / await
.
function square(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (typeof num !== 'number') {
reject(new TypeError(`Expected number but got: ${typeof num}`));
}
const result = num * num;
resolve(result);
}, 100);
});
}
square('8')
.then((result) => console.log(result))
.catch((err) => console.error(err));
Источники событий
Другой шаблон, подходящий для работы с длительными асинхронными операциями, которые могут приводить к множественным ошибкам или результатам, — это возврат EventEmitter
из функции и выдача события как для успешного, так и для неудачного случая:
const { EventEmitter } = require('events');
function emitCount() {
const emitter = new EventEmitter();
let count = 0;
// Async operation
const interval = setInterval(() => {
count++;
if (count % 4 == 0) {
emitter.emit(
'error',
new Error(`Something went wrong on count: ${count}`)
);
return;
}
emitter.emit('success', count);
if (count === 10) {
clearInterval(interval);
emitter.emit('end');
}
}, 1000);
return emitter;
}
Функция emitCount ()
возвращает новый эмиттер событий, который сообщает об успешном исходе в асинхронной операции. Она увеличивает значение переменной count
и каждую секунду генерирует событие успеха и событие ошибки, если значение count
делится на 4. Когда count
достигает 10, генерируется событие завершения. Этот шаблон позволяет передавать результаты по мере их поступления вместо ожидания завершения всей операции.
Вот как можно отслеживать и реагировать на каждое из событий, генерируемых функцией emitCount ()
:
const counter = emitCount();
counter.on('success', (count) => {
console.log(`Count is: ${count}`);
});
counter.on('error', (err) => {
console.error(err.message);
});
counter.on('end', () => {
console.info('Counter has ended');
});
Функция колбэка для каждого прослушивателя событий выполняется независимо, как только событие генерируется. Событие ошибки (error) — это особый случай для Node.js, потому что при отсутствии прослушивателя процесс Node.js выходит из строя. Вы можете закомментировать прослушиватель событий ошибки выше и запустить программу, чтобы увидеть, что произойдёт.
Расширение объекта ошибки
Необходимо создавать собственные пользовательские классы ошибок, чтобы лучше отражать разные типы ошибок: класс ValidationError
для ошибок, возникающих при проверке пользовательского ввода, класс DatabaseError
для операций с базами данных, TimeoutError
для операций, для которых истекло назначенное им время ожидания.
Пользовательские классы ошибок, расширяющие объект Error
, сохранят основные свойства ошибки: сообщение (message
), имя (name
) и стек (stack
). Но у них есть собственные свойства. ValidationError
можно улучшить, добавив значимые свойства — часть ввода, вызвавшую ошибку.
Вот как можно расширить встроенный объект Error
в Node.js:
class ApplicationError extends Error {
constructor(message) {
super(message);
// name is set to the name of the class
this.name = this.constructor.name;
}
}
class ValidationError extends ApplicationError {
constructor(message, cause) {
super(message);
this.cause = cause
}
}
Класс ApplicationError
— общая ошибка, а класс ValidationError
представляет любую ошибку, возникающую при проверке ввода данных пользователем. Он наследуется от класса ApplicationError
и дополняет его свойством cause
для указания ввода, вызвавшего ошибку. Пользовательские классы ошибки можно использовать, как и обычные:
function validateInput(input) {
if (!input) {
throw new ValidationError('Only truthy inputs allowed', input);
}
return input;
}
try {
validateInput(userJson);
} catch (err) {
if (err instanceof ValidationError) {
console.error(`Validation error: ${err.message}, caused by: ${err.cause}`);
return;
}
console.error(`Other error: ${err.message}`);
}
Ключевое слово instanceof
следует использовать для проверки конкретного типа ошибки. Не используйте имя ошибки для проверки типа, как в err.name === 'ValidationError'
: это не сработает, если ошибка получена из подкласса ValidationError
.
Типы ошибок
Типы ошибок можно разделить на две основные категории: ошибки программиста и операционные проблемы. К первому типу можно отнести неудачные или неправильные аргументы функции, в то время как временные сбои при работе с внешними API однозначно относятся ко второй категории.
Операционные ошибки
Операционные ошибки — это предсказуемые ошибки, которые возникают в процессе выполнения приложения. Это не обязательно баги, чаще это даже внешние обстоятельства, способные нарушить ход выполнения программы. В таких случаях можно полностью понять влияние ошибки на процессы:
-
Запрос API не выполняется по какой-либо причине (например, сервер не работает или превышен лимит скорости).
-
Соединение с базой данных потеряно, например, из-за неисправного сетевого соединения.
-
ОС не может выполнить запрос на открытие файла или запись в него.
-
Пользователь отправляет на сервер недопустимые данные: неверный номер телефона или адрес электронной почты.
Ошибки программиста
Ошибки программиста — это ошибки в логике или синтаксисе программы, которые можно исправить только путём изменения исходного кода. Ошибки этого типа невозможно обработать, потому что это недочёты в программе:
-
Синтаксические ошибки: незакрытая фигурная скобка.
-
Ошибки типа при попытке сделать что-то неправильное: выполнение операций с операндами несовпадающих типов.
-
Неверные параметры при вызове функции.
-
Ссылки на ошибки при неправильном написании имени переменной, функции или свойства.
-
Попытка получить доступ к местоположению за концом массива.
-
Неспособность обработать операционную ошибку.
Обработка операционных ошибок
Операционные ошибки в большинстве случаев предсказуемы. Их обработка — это рассмотрение вероятности неудачного завершения операции, возможных причин и последствий. Рассмотрим несколько стратегий обработки операционных ошибок в Node.js.
Сообщить об ошибке в стек
Во многих случаях лучше остановить выполнение программы, очистить все незавершённые процессы и сообщить об ошибке в стек. Зачастую это единственный способ исправить ошибку, когда функция, в которой она возникла, находится дальше по стеку.
Повторить операцию
Сетевые запросы к внешним службам иногда могут завершаться ошибкой, даже если запрос полностью верен. Это случается из-за сбоя и неполадках сети или перегрузке сервера. Можно повторить запрос несколько раз, пока он не будет успешно завершён или пока не будет достигнуто максимальное количество повторных попыток. Первое, что нужно сделать, — это определить, уместно ли повторить запрос. Если исходный код состояния HTTP ответа — 500, 503 или 429, повторте запрос через некоторое время.
Проверьте, присутствует ли в ответе HTTP-заголовок Retry-After. Он указывает на точное время ожидания перед выполнением последующего запроса. Если его нет, необходимо отложить последующий запрос и постепенно увеличивать временной промежуток для каждой повторной попытки. Этот метод известен как стратегия экспоненциального отката. Нужно ещё определить максимальное время задержки и число запросов до отказа от дальнейших попыток.
Отправить ошибку клиенту
По умолчанию пользователи вводят данные неправильно. Поэтому первое, что нужно сделать перед запуском каких-либо процессов, — проверить введённые данные и незамедлительно сообщить пользователю о любых ошибках. При обработке ошибок клиента обязательно включите всю информацию, необходимую для создания сообщения об ошибке и имеющую смысл для пользователя.
Прервать программу.
В случае неисправимых системных ошибок разумный выход — зарегистрировать ошибку и немедленно завершить работу программы. Если исключение невозможно исправить на уровне JavaScript, то, возможно, не получится корректно завершить работу сервера. Тогда нужен системный администратор, способный всё исправить.
Предотвращение ошибок программиста
Ошибки программиста сами по себе не могут быть обработаны, потому что их причина в коде или в логике. Однако ошибаться можно реже.
Принять TypeScript
TypeScript — это строго типизированное надмножество JavaScript. Основная цель его проектирования — статическая идентификация потенциально ошибочных конструкций без штрафных санкций во время выполнения. Принимая TypeScript в проекте (с максимально возможными параметрами компилятора), можно устранить целый класс ошибок программиста в ходе компиляции.
Когда проект на TypeScript, такие ошибки, как undefined
is not a function
, синтаксические или ссылочные ошибки, исчезают из кодовой базы. Перенос на TypeScript можно выполнять постепенно. Для быстрой миграции есть инструмент ts-migrate.
Определить поведение для неверных параметров
Многие ошибки возникают из-за передачи неверных параметров. Это может быть связано не только с очевидными ошибками, такими как передача строки вместо числа, но и с небольшими погрешностями, когда аргумент функции имеет правильный тип, но выходит за пределы диапазона, который функция способна обработать. Когда функция вызывается таким образом, она может выдать неверное значение, например NaN
. Когда сбой обнаруживается, сперва трудно определить его причину.
При работе с неверными параметрами и определяйте их поведение, либо выдавая ошибку, либо возвращая специальное значение, такое как null
, undefined
или -1
, когда проблема может быть решена локально. Первый вариант— это подход, используемый JSON.parse ()
, который выдаёт исключение SyntaxError
, если строка для синтаксического анализа недействительна. Второй вариант — метод string.indexOf ()
.
Автоматизированное тестирование
Автоматизированные наборы тестов повышает вероятность исправления ошибок. Тесты помогают выяснить, как функция работает с нетипичными значениями. Для модульного тестирования подходят среды, такие как Jest или Mocha.
Неперехваченные исключения и необработанные отклонения обещаний
Неперехваченные исключения и необработанные отклонения обещаний вызываются ошибками программиста. Событие uncaughtException
генерируется, когда исключение не перехватывается до того как достигнет цикла обработки событий. При обнаружении неперехваченного исключения приложение немедленно выходит из строя. Для переопределения такого поведения всегда можно добавить обработчик события:
// unsafe
process.on('uncaughtException', (err) => {
console.error(err);
});
Но неперехваченное исключение указывает на то, что приложение находится в неопределённом состоянии. Поэтому попытка возобновить работу в обычном режиме без восстановления после ошибки небезопасна и может привести к утечке памяти и зависанию сокетов. Лучше использовать обработчик uncaught Exception
для очистки всех выделенных ресурсов, закрытия соединений и ведения лога ошибок для оценки перед завершением процесса.
// better
process.on('uncaughtException', (err) => {
Honeybadger.notify(error); // log the error in a permanent storage
// attempt a gracefully shutdown
server.close(() => {
process.exit(1); // then exit
});
// If a graceful shutdown is not achieved after 1 second,
// shut down the process completely
setTimeout(() => {
process.abort(); // exit immediately and generate a core dump file
}, 1000).unref()
});
Событие unhandledRejection
генерируется, когда отклонённое обещание не обрабатывается блоком catch
. В отличие от uncaughtException
, эти события не вызывают немедленного сбоя приложения. Однако необработанные отклонения обещаний сейчас признаны устаревшими и могут немедленно завершить процесс в следующих релизах Node.js. Отслеживать необработанные отклонения обещаний можно с помощью прослушивателя событий unhandledRejection
:
process.on('unhandledRejection', (reason, promise) => {
Honeybadger.notify({
message: 'Unhandled promise rejection',
params: {
promise,
reason,
},
});
server.close(() => {
process.exit(1);
});
setTimeout(() => {
process.abort();
}, 1000).unref()
});
Серверы необходимо запускать с помощью диспетчера процессов, который автоматически перезапустит их в случае сбоя. Распространённый вариант — PM2, но для Linux существуют также systemd
и upstart
, а пользователи Docker могут использовать собственную политику перезапуска. По завершении всех процессов стабильное обслуживание будет восстановлено почти мгновенно, а у вас будт информация о неперехваченном исключении. Можно запутсить несколько процессов и применить балансировщик нагрузки для распределения входящих запросов. Это поможет предотвратить простои.
Централизованная отчётность об ошибках
Ни одна стратегия обработки ошибок не будет полной без надёжной стратегии ведения журнала ошибок. Когда происходит сбой, важно узаписать как можно больше информации о проблеме. Централизация логов позволяет оценить, что происходит в коде.
Honeybadger предоставляет всё необходимое для отслеживания ошибок. Интегрируется так:
Установите пакет
Используйте npm для установки пакета:
$ npm install @honeybadger-io/js --save
Импортируйте библиотеку
Импортируйте библиотеку и настройте её с помощью ключа API, чтобы получать сообщения об ошибках:
const Honeybadger = require('@honeybadger-io/js');
Honeybadger.configure({
apiKey: '[ YOUR API KEY HERE ]'
});
Сообщите об ошибках
Метоодом notify ():
try {
// ...error producing code
} catch(error) {
Honeybadger.notify(error);
}
Просмотрите полную документацию или ознакомьтесь с образцом Node.js / Express на GitHub.
Без обработки ошибок не бывает надёжного софта.
Спасибо за внимание и удачного кода!