Suppose I’m calling 3 javascript (jquery) files like this:
<script type="text/javascript" src="js/file1.js"></script>
<script type="text/javascript" src="js/file2.js"></script>
<script type="text/javascript" src="js/file3.js"></script>
On file2.js there are script errors (or something with a function goes wrong): for example, it says «function does not exist». Because there is an error in file2.js, scripts in file3.js are not executed.
Is there is a way to make sure scripts in file3.js are still executed? Or, is it possible to bypass or ignore the error on file2.js?
STerliakov
5,0603 gold badges15 silver badges37 bronze badges
asked Apr 19, 2013 at 3:08
3
You can use a try catch block around the code with the error
try{
//code that causes an error
}catch(e){
functionToHandleError(e);
}
//continue from here
But you should only do that if the error is a known condition/unavoidable problem with an external library or only happens on bad user input. You shouldn’t use it to allow the program to keep running at all costs.
Some ways to avoid errors
- check that functions exist before calling them
- check that an object exists before referencing its properties
- if user input is involved, validate the input to check that it’s the expected type/range/etc
- if this is a loading order problem consider reordering your scripts, or if this is a larger application using a dependency management system like requireJS
- if objects are undefined after an asynchronous request, remember that code does not wait on an async request and it will not be defined until afterwards. Code dealing with the provided values should be done in a callback function or using deffereds/promises.
- if you’re referencing dom elements, consider starting your JS code from within a function that runs when the DOM is loaded such as jQuery’s document.ready or the native window.onload.
- if javascript variables are declared without the
var
keyword, or are attached to a global object, they can be modified in places other than they are declared. Javascript also doesn’t have block scope. Make sure you know all the places your variables are being modified, and try to limit the scope of your variables when possible. Especially avoid using too many global variables to hold state, as it can be difficult to know the current state without many null/type checks.
Some ways to handle errors
-
If its an expected problem (IE if the input may be empty and requires different handling), ideally check it with an if statement beforehand. If you don’t want to do that, you can use try/catch to cast the input, ask for new input, or act on the input given as approperiate.
-
if its an unexpected problem (IE you don’t know whats going on) a catch block can be a good place to output diagnostic information during development. IE, don’t just catch it and let it fail silently, output a message to let you or other developers know where it failed, and then use your favorite browser’s web developer tools to inspect the values at the time, figure out what happened, and add handling for that case to fix it.
What not to do
-
put the try/catch in, and then just let the program keep on running. If the code that isn’t running correctly is unnecessary, get rid of it. If it is necessary and isn’t working correctly, fix it.
-
use try catches as your input validation (IE if the function is null it will throw an exception and I’ll catch it, then I know the function is null). Whenever possible, check for any expected errors. Try/catches should handle «exceptions» when the behavior is unusual and you need to go down an alternative code path or alert the user that something is wrong.
answered Apr 19, 2013 at 3:12
Ben McCormickBen McCormick
25.3k12 gold badges52 silver badges71 bronze badges
1
When you don’t have time to debug try this:
function stoperror() {
return true;
}
Call it:
window.onerror = stoperror;
The functionality is very simple: create a function that always returns true, and then whenever an error occurs, call this function (returning true and suppressing the error).
trincot
319k35 gold badges246 silver badges287 bronze badges
answered Sep 16, 2014 at 17:57
1
you can use try and catch statements.And check for null by using != null
condition
try{
}catch(e){
}
answered Apr 19, 2013 at 3:13
PSRPSR
39.8k42 gold badges112 silver badges151 bronze badges
Wrap the call that results in an error in a try…catch block:
try {
callNonExistentMethod();
}
catch (e) {
//Handle the error if you wish.
}
answered Apr 19, 2013 at 3:12
Justin HelgersonJustin Helgerson
24.9k17 gold badges98 silver badges124 bronze badges
you can save yourself from «function is undefined» by using this
//Simple function that will tell if the function is defined or not
function is_function(func) {
return typeof window[func] !== 'undefined' && $.isFunction(window[func]);
}
//usage
if (is_function("myFunction") {
alert("myFunction defined");
} else {
alert("myFunction not defined");
}
trincot
319k35 gold badges246 silver badges287 bronze badges
answered Apr 23, 2015 at 13:16
I would suggest adding proper null checks around objects(including functions)
before assuming they exist,so that the code will fail gracefully.
Here are some ideas:
Javascript check if function exists
answered Apr 19, 2013 at 3:13
TGHTGH
38.8k12 gold badges102 silver badges135 bronze badges
Ошибки — это хорошо. Автор материала, перевод которого мы сегодня публикуем, говорит, что уверен в том, что эта идея известна всем. На первый взгляд ошибки кажутся чем-то страшным. Им могут сопутствовать какие-то потери. Ошибка, сделанная на публике, вредит авторитету того, кто её совершил. Но, совершая ошибки, мы на них учимся, а значит, попадая в следующий раз в ситуацию, в которой раньше вели себя неправильно, делаем всё как нужно.
Выше мы говорили об ошибках, которые люди совершают в обычной жизни. Ошибки в программировании — это нечто иное. Сообщения об ошибках помогают нам улучшать код, они позволяют сообщать пользователям наших проектов о том, что что-то пошло не так, и, возможно, рассказывают пользователям о том, как нужно вести себя для того, чтобы ошибок больше не возникало.
Этот материал, посвящённый обработке ошибок в JavaScript, разбит на три части. Сначала мы сделаем общий обзор системы обработки ошибок в JavaScript и поговорим об объектах ошибок. После этого мы поищем ответ на вопрос о том, что делать с ошибками, возникающими в серверном коде (в частности, при использовании связки Node.js + Express.js). Далее — обсудим обработку ошибок в React.js. Фреймворки, которые будут здесь рассматриваться, выбраны по причине их огромной популярности. Однако рассматриваемые здесь принципы работы с ошибками универсальны, поэтому вы, даже если не пользуетесь Express и React, без труда сможете применить то, что узнали, к тем инструментам, с которыми работаете.
Код демонстрационного проекта, используемого в данном материале, можно найти в этом репозитории.
1. Ошибки в JavaScript и универсальные способы работы с ними
Если в вашем коде что-то пошло не так, вы можете воспользоваться следующей конструкцией.
throw new Error('something went wrong')
В ходе выполнения этой команды будет создан экземпляр объекта Error и будет сгенерировано (или, как говорят, «выброшено») исключение с этим объектом. Инструкция throw может генерировать исключения, содержащие произвольные выражения. При этом выполнение скрипта остановится в том случае, если не были предприняты меры по обработке ошибки.
Начинающие JS-программисты обычно не используют инструкцию throw
. Они, как правило, сталкиваются с исключениями, выдаваемыми либо средой выполнения языка, либо сторонними библиотеками. Когда это происходит — в консоль попадает нечто вроде ReferenceError: fs is not defined
и выполнение программы останавливается.
▍Объект Error
У экземпляров объекта Error
есть несколько свойств, которыми мы можем пользоваться. Первое интересующее нас свойство — message
. Именно сюда попадает та строка, которую можно передать конструктору ошибки в качестве аргумента. Например, ниже показано создание экземпляра объекта Error
и вывод в консоль переданной конструктором строки через обращение к его свойству message
.
const myError = new Error('please improve your code')
console.log(myError.message) // please improve your code
Второе свойство объекта, очень важное, представляет собой трассировку стека ошибки. Это — свойство stack
. Обратившись к нему можно просмотреть стек вызовов (историю ошибки), который показывает последовательность операций, приведшую к неправильной работе программы. В частности, это позволяет понять — в каком именно файле содержится сбойный код, и увидеть, какая последовательность вызовов функций привела к ошибке. Вот пример того, что можно увидеть, обратившись к свойству stack
.
Error: please improve your code
at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
at startup (internal/bootstrap/node.js:266:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)
Здесь, в верхней части, находится сообщение об ошибке, затем следует указание на тот участок кода, выполнение которого вызвало ошибку, потом описывается то место, откуда был вызван этот сбойный участок. Это продолжается до самого «дальнего» по отношению к ошибке фрагмента кода.
▍Генерирование и обработка ошибок
Создание экземпляра объекта Error
, то есть, выполнение команды вида new Error()
, ни к каким особым последствиям не приводит. Интересные вещи начинают происходить после применения оператора throw
, который генерирует ошибку. Как уже было сказано, если такую ошибку не обработать, выполнение скрипта остановится. При этом нет никакой разницы — был ли оператор throw
использован самим программистом, произошла ли ошибка в некоей библиотеке или в среде выполнения языка (в браузере или в Node.js). Поговорим о различных сценариях обработки ошибок.
▍Конструкция try…catch
Блок try...catch
представляет собой самый простой способ обработки ошибок, о котором часто забывают. В наши дни, правда, он используется гораздо интенсивнее чем раньше, благодаря тому, что его можно применять для обработки ошибок в конструкциях async/await
.
Этот блок можно использовать для обработки любых ошибок, происходящих в синхронном коде. Рассмотрим пример.
const a = 5
try {
console.log(b) // переменная b не объявлена - возникает ошибка
} catch (err) {
console.error(err) // в консоль попадает сообщение об ошибке и стек ошибки
}
console.log(a) // выполнение скрипта не останавливается, данная команда выполняется
Если бы в этом примере мы не заключили бы сбойную команду console.log(b)
в блок try...catch
, то выполнение скрипта было бы остановлено.
▍Блок finally
Иногда случается так, что некий код нужно выполнить независимо от того, произошла ошибка или нет. Для этого можно, в конструкции try...catch
, использовать третий, необязательный, блок — finally
. Часто его использование эквивалентно некоему коду, который идёт сразу после try...catch
, но в некоторых ситуациях он может пригодиться. Вот пример его использования.
const a = 5
try {
console.log(b) // переменная b не объявлена - возникает ошибка
} catch (err) {
console.error(err) // в консоль попадает сообщение об ошибке и стек ошибки
} finally {
console.log(a) // этот код будет выполнен в любом случае
}
▍Асинхронные механизмы — коллбэки
Программируя на JavaScript всегда стоит обращать внимание на участки кода, выполняющиеся асинхронно. Если у вас имеется асинхронная функция и в ней возникает ошибка, скрипт продолжит выполняться. Когда асинхронные механизмы в JS реализуются с использованием коллбэков (кстати, делать так не рекомендуется), соответствующий коллбэк (функция обратного вызова) обычно получает два параметра. Это нечто вроде параметра err
, который может содержать ошибку, и result
— с результатами выполнения асинхронной операции. Выглядит это примерно так:
myAsyncFunc(someInput, (err, result) => {
if(err) return console.error(err) // порядок работы с объектом ошибки мы рассмотрим позже
console.log(result)
})
Если в коллбэк попадает ошибка, она видна там в виде параметра err
. В противном случае в этот параметр попадёт значение undefined
или null
. Если оказалось, что в err
что-то есть, важно отреагировать на это, либо так как в нашем примере, воспользовавшись командой return
, либо воспользовавшись конструкцией if...else
и поместив в блок else
команды для работы с результатом выполнения асинхронной операции. Речь идёт о том, чтобы, в том случае, если произошла ошибка, исключить возможность работы с результатом, параметром result
, который в таком случае может иметь значение undefined
. Работа с таким значением, если предполагается, например, что оно содержит объект, сама может вызвать ошибку. Скажем, это произойдёт при попытке использовать конструкцию result.data
или подобную ей.
▍Асинхронные механизмы — промисы
Для выполнения асинхронных операций в JavaScript лучше использовать не коллбэки а промисы. Тут, в дополнение к улучшенной читабельности кода, имеются и более совершенные механизмы обработки ошибок. А именно, возиться с объектом ошибки, который может попасть в функцию обратного вызова, при использовании промисов не нужно. Здесь для этой цели предусмотрен специальный блок catch
. Он перехватывает все ошибки, произошедшие в промисах, которые находятся до него, или все ошибки, которые произошли в коде после предыдущего блока catch
. Обратите внимание на то, что если в промисе произошла ошибка, для обработки которой нет блока catch
, это не остановит выполнение скрипта, но сообщение об ошибке будет не особенно удобочитаемым.
(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong
(node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */
В результате можно порекомендовать всегда, при работе с промисами, использовать блок catch
. Взглянем на пример.
Promise.resolve(1)
.then(res => {
console.log(res) // 1
throw new Error('something went wrong')
return Promise.resolve(2)
})
.then(res => {
console.log(res) // этот блок выполнен не будет
})
.catch(err => {
console.error(err) // о том, что делать с этой ошибкой, поговорим позже
return Promise.resolve(3)
})
.then(res => {
console.log(res) // 3
})
.catch(err => {
// этот блок тут на тот случай, если в предыдущем блоке возникнет какая-нибудь ошибка
console.error(err)
})
▍Асинхронные механизмы и try…catch
После того, как в JavaScript появилась конструкция async/await
, мы вернулись к классическому способу обработки ошибок — к try...catch...finally
. Обрабатывать ошибки при таком подходе оказывается очень легко и удобно. Рассмотрим пример.
;(async function() {
try {
await someFuncThatThrowsAnError()
} catch (err) {
console.error(err) // об этом поговорим позже
}
console.log('Easy!') // будет выполнено
})()
При таком подходе ошибки в асинхронном коде обрабатываются так же, как в синхронном. В результате теперь, при необходимости, в одном блоке catch
можно обрабатывать более широкий диапазон ошибок.
2. Генерирование и обработка ошибок в серверном коде
Теперь, когда у нас есть инструменты для работы с ошибками, посмотрим на то, что мы можем с ними делать в реальных ситуациях. Генерирование и правильная обработка ошибок — это важнейший аспект серверного программирования. Существуют разные подходы к работе с ошибками. Здесь будет продемонстрирован подход с использованием собственного конструктора для экземпляров объекта Error
и кодов ошибок, которые удобно передавать во фронтенд или любым механизмам, использующим серверные API. Как структурирован бэкенд конкретного проекта — особого значения не имеет, так как при любом подходе можно использовать одни и те же идеи, касающиеся работы с ошибками.
В качестве серверного фреймворка, отвечающего за маршрутизацию, мы будем использовать Express.js. Подумаем о том, какая структура нам нужна для организации эффективной системы обработки ошибок. Итак, вот что нам нужно:
- Универсальная обработка ошибок — некий базовый механизм, подходящий для обработки любых ошибок, в ходе работы которого просто выдаётся сообщение наподобие
Something went wrong, please try again or contact us
, предлагающее пользователю попробовать выполнить операцию, давшую сбой, ещё раз или связаться с владельцем сервера. Эта система не отличается особой интеллектуальностью, но она, по крайней мере, способна сообщить пользователю о том, что что-то пошло не так. Подобное сообщение гораздо лучше, чем «бесконечная загрузка» или нечто подобное. - Обработка конкретных ошибок — механизм, позволяющий сообщить пользователю подробные сведения о причинах неправильного поведения системы и дать ему конкретные советы по борьбе с неполадкой. Например, это может касаться отсутствия неких важных данных в запросе, который пользователь отправляет на сервер, или в том, что в базе данных уже существует некая запись, которую он пытается добавить ещё раз, и так далее.
▍Разработка собственного конструктора объектов ошибок
Здесь мы воспользуемся стандартным классом Error
и расширим его. Пользоваться механизмами наследования в JavaScript — дело рискованное, но в данном случае эти механизмы оказываются весьма полезными. Зачем нам наследование? Дело в том, что нам, для того, чтобы код удобно было бы отлаживать, нужны сведения о трассировке стека ошибки. Расширяя стандартный класс Error
, мы, без дополнительных усилий, получаем возможности по трассировке стека. Мы добавляем в наш собственный объект ошибки два свойства. Первое — это свойство code
, доступ к которому можно будет получить с помощью конструкции вида err.code
. Второе — свойство status
. В него будет записываться код состояния HTTP, который планируется передавать клиентской части приложения.
Вот как выглядит класс CustomError
, код которого оформлен в виде модуля.
class CustomError extends Error {
constructor(code = 'GENERIC', status = 500, ...params) {
super(...params)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomError)
}
this.code = code
this.status = status
}
}
module.exports = CustomError
▍Маршрутизация
Теперь, когда наш объект ошибки готов к использованию, нужно настроить структуру маршрутов. Как было сказано выше, нам требуется реализовать унифицированный подход к обработке ошибок, позволяющий одинаково обрабатывать ошибки для всех маршрутов. По умолчанию фреймворк Express.js не вполне поддерживает такую схему работы. Дело в том, что все его маршруты инкапсулированы.
Для того чтобы справиться с этой проблемой, мы можем реализовать собственный обработчик маршрутов и определять логику маршрутов в виде обычных функций. Благодаря такому подходу, если функция маршрута (или любая другая функция) выбрасывает ошибку, она попадёт в обработчик маршрутов, который затем может передать её клиентской части приложения. При возникновении ошибки на сервере мы планируем передавать её во фронтенд в следующем формате, полагая, что для этого будет применяться JSON-API:
{
error: 'SOME_ERROR_CODE',
description: 'Something bad happened. Please try again or contact support.'
}
Если на данном этапе происходящие кажется вам непонятным — не беспокойтесь — просто продолжайте читать, пробуйте работать с тем, о чём идёт речь, и постепенно вы во всём разберётесь. На самом деле, если говорить о компьютерном обучении, здесь применяется подход «сверху-вниз», когда сначала обсуждаются общие идеи, а потом осуществляется переход к частностям.
Вот как выглядит код обработчика маршрутов.
const express = require('express')
const router = express.Router()
const CustomError = require('../CustomError')
router.use(async (req, res) => {
try {
const route = require(`.${req.path}`)[req.method]
try {
const result = route(req) // Передаём запрос функции route
res.send(result) // Передаём клиенту то, что получено от функции route
} catch (err) {
/*
Сюда мы попадаем в том случае, если в функции route произойдёт ошибка
*/
if (err instanceof CustomError) {
/*
Если ошибка уже обработана - трансформируем её в
возвращаемый объект
*/
return res.status(err.status).send({
error: err.code,
description: err.message,
})
} else {
console.error(err) // Для отладочных целей
// Общая ошибка - вернём универсальный объект ошибки
return res.status(500).send({
error: 'GENERIC',
description: 'Something went wrong. Please try again or contact support.',
})
}
}
} catch (err) {
/*
Сюда мы попадём, если запрос окажется неудачным, то есть,
либо не будет найдено файла, соответствующего пути, переданному
в запросе, либо не будет экспортированной функции с заданным
методом запроса
*/
res.status(404).send({
error: 'NOT_FOUND',
description: 'The resource you tried to access does not exist.',
})
}
})
module.exports = router
Полагаем, комментарии в коде достаточно хорошо его поясняют. Надеемся, читать их удобнее, чем объяснения подобного кода, данные после него.
Теперь взглянем на файл маршрутов.
const CustomError = require('../CustomError')
const GET = req => {
// пример успешного выполнения запроса
return { name: 'Rio de Janeiro' }
}
const POST = req => {
// пример ошибки общего характера
throw new Error('Some unexpected error, may also be thrown by a library or the runtime.')
}
const DELETE = req => {
// пример ошибки, обрабатываемой особым образом
throw new CustomError('CITY_NOT_FOUND', 404, 'The city you are trying to delete could not be found.')
}
const PATCH = req => {
// пример перехвата ошибок и использования CustomError
try {
// тут случилось что-то нехорошее
throw new Error('Some internal error')
} catch (err) {
console.error(err) // принимаем решение о том, что нам тут делать
throw new CustomError(
'CITY_NOT_EDITABLE',
400,
'The city you are trying to edit is not editable.'
)
}
}
module.exports = {
GET,
POST,
DELETE,
PATCH,
}
В этих примерах с самими запросами ничего не делается. Тут просто рассматриваются разные сценарии возникновения ошибок. Итак, например, запрос GET /city
попадёт в функцию const GET = req =>...
, запрос POST /city
попадёт в функцию const POST = req =>...
и так далее. Эта схема работает и при использовании параметров запросов. Например — для запроса вида GET /city?startsWith=R
. В целом, здесь продемонстрировано, что при обработке ошибок, во фронтенд может попасть либо общая ошибка, содержащая лишь предложение попробовать снова или связаться с владельцем сервера, либо ошибка, сформированная с использованием конструктора CustomError
, которая содержит подробные сведения о проблеме.
Данные общей ошибки придут в клиентскую часть приложения в таком виде:
{
error: 'GENERIC',
description: 'Something went wrong. Please try again or contact support.'
}
Конструктор CustomError
используется так:
throw new CustomError('MY_CODE', 400, 'Error description')
Это даёт следующий JSON-код, передаваемый во фронтенд:
{
error: 'MY_CODE',
description: 'Error description'
}
Теперь, когда мы основательно потрудились над серверной частью приложения, в клиентскую часть больше не попадают бесполезные логи ошибок. Вместо этого клиент получает полезные сведения о том, что пошло не так.
Не забудьте о том, что здесь лежит репозиторий с рассматриваемым здесь кодом. Можете его загрузить, поэкспериментировать с ним, и, если надо, адаптировать под нужды вашего проекта.
3. Работа с ошибками на клиенте
Теперь пришла пора описать третью часть нашей системы обработки ошибок, касающуюся фронтенда. Тут нужно будет, во-первых, обрабатывать ошибки, возникающие в клиентской части приложения, а во-вторых, понадобится оповещать пользователя об ошибках, возникающих на сервере. Разберёмся сначала с показом сведений о серверных ошибках. Как уже было сказано, в этом примере будет использована библиотека React.
▍Сохранение сведений об ошибках в состоянии приложения
Как и любые другие данные, ошибки и сообщения об ошибках могут меняться, поэтому их имеет смысл помещать в состояние компонентов. При монтировании компонента данные об ошибке сбрасываются, поэтому, когда пользователь впервые видит страницу, там сообщений об ошибках не будет.
Следующее, с чем надо разобраться, заключается в том, что ошибки одного типа нужно показывать в одном стиле. По аналогии с сервером, здесь можно выделить 3 типа ошибок.
- Глобальные ошибки — в эту категорию попадают сообщения об ошибках общего характера, приходящие с сервера, или ошибки, которые, например, возникают в том случае, если пользователь не вошёл в систему и в других подобных ситуациях.
- Специфические ошибки, выдаваемые серверной частью приложения — сюда относятся ошибки, сведения о которых приходят с сервера. Например, подобная ошибка возникает, если пользователь попытался войти в систему и отправил на сервер имя и пароль, а сервер сообщил ему о том, что пароль неправильный. Подобные вещи в клиентской части приложения не проверяются, поэтому сообщения о таких ошибках должны приходить с сервера.
- Специфические ошибки, выдаваемые клиентской частью приложения. Пример такой ошибки — сообщение о некорректном адресе электронной почты, введённом в соответствующее поле.
Ошибки второго и третьего типов очень похожи, работать с ними можно, используя хранилище состояния компонентов одного уровня. Их главное различие заключается в том, что они исходят из разных источников. Ниже, анализируя код, мы посмотрим на работу с ними.
Здесь будет использоваться встроенная в React система управления состоянием приложения, но, при необходимости, вы можете воспользоваться и специализированными решениями для управления состоянием — такими, как MobX или Redux.
▍Глобальные ошибки
Обычно сообщения о таких ошибках сохраняются в компоненте наиболее высокого уровня, имеющем состояние. Они выводятся в статическом элементе пользовательского интерфейса. Это может быть красное поле в верхней части экрана, модальное окно или что угодно другое. Реализация зависит от конкретного проекта. Вот как выглядит сообщение о такой ошибке.
Сообщение о глобальной ошибке
Теперь взглянем на код, который хранится в файле Application.js
.
import React, { Component } from 'react'
import GlobalError from './GlobalError'
class Application extends Component {
constructor(props) {
super(props)
this.state = {
error: '',
}
this._resetError = this._resetError.bind(this)
this._setError = this._setError.bind(this)
}
render() {
return (
<div className="container">
<GlobalError error={this.state.error} resetError={this._resetError} />
<h1>Handling Errors</h1>
</div>
)
}
_resetError() {
this.setState({ error: '' })
}
_setError(newError) {
this.setState({ error: newError })
}
}
export default Application
Как видно, в состоянии, в Application.js
, имеется место для хранения данных ошибки. Кроме того, тут предусмотрены методы для сброса этих данных и для их изменения.
Ошибка и метод для сброса ошибки передаётся компоненту GlobalError
, который отвечает за вывод сообщения об ошибке на экран и за сброс ошибки после нажатия на значок x
в поле, где выводится сообщение. Вот код компонента GlobalError
(файл GlobalError.js
).
import React, { Component } from 'react'
class GlobalError extends Component {
render() {
if (!this.props.error) return null
return (
<div
style={{
position: 'fixed',
top: 0,
left: '50%',
transform: 'translateX(-50%)',
padding: 10,
backgroundColor: '#ffcccc',
boxShadow: '0 3px 25px -10px rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
}}
>
{this.props.error}
<i
className="material-icons"
style={{ cursor: 'pointer' }}
onClick={this.props.resetError}
>
close
</font></i>
</div>
)
}
}
export default GlobalError
Обратите внимание на строку if (!this.props.error) return null
. Она указывает на то, что при отсутствии ошибки компонент ничего не выводит. Это предотвращает постоянный показ красного прямоугольника на странице. Конечно, вы, при желании, можете поменять внешний вид и поведение этого компонента. Например, вместо того, чтобы сбрасывать ошибку по нажатию на x
, можно задать тайм-аут в пару секунд, по истечении которого состояние ошибки сбрасывается автоматически.
Теперь, когда всё готово для работы с глобальными ошибками, для задания глобальной ошибки достаточно воспользоваться _setError
из Application.js
. Например, это можно сделать в том случае, если сервер, после обращения к нему, вернул сообщение об общей ошибке (error: 'GENERIC'
). Рассмотрим пример (файл GenericErrorReq.js
).
import React, { Component } from 'react'
import axios from 'axios'
class GenericErrorReq extends Component {
constructor(props) {
super(props)
this._callBackend = this._callBackend.bind(this)
}
render() {
return (
<div>
<button onClick={this._callBackend}>Click me to call the backend</button>
</div>
)
}
_callBackend() {
axios
.post('/api/city')
.then(result => {
// сделать что-нибудь с результатом в том случае, если запрос оказался успешным
})
.catch(err => {
if (err.response.data.error === 'GENERIC') {
this.props.setError(err.response.data.description)
}
})
}
}
export default GenericErrorReq
На самом деле, на этом наш разговор об обработке ошибок можно было бы и закончить. Даже если в проекте нужно оповещать пользователя о специфических ошибках, никто не мешает просто поменять глобальное состояние, хранящее ошибку и вывести соответствующее сообщение поверх страницы. Однако тут мы не остановимся и поговорим о специфических ошибках. Во-первых, это руководство по обработке ошибок иначе было бы неполным, а во-вторых, с точки зрения UX-специалистов, неправильно будет показывать сообщения обо всех ошибках так, будто все они — глобальные.
▍Обработка специфических ошибок, возникающих при выполнении запросов
Вот пример специфического сообщения об ошибке, выводимого в том случае, если пользователь пытается удалить из базы данных город, которого там нет.
Сообщение о специфической ошибке
Тут используется тот же принцип, который мы применяли при работе с глобальными ошибками. Только сведения о таких ошибках хранятся в локальном состоянии соответствующих компонентов. Работа с ними очень похожа на работу с глобальными ошибками. Вот код файла SpecificErrorReq.js
.
import React, { Component } from 'react'
import axios from 'axios'
import InlineError from './InlineError'
class SpecificErrorRequest extends Component {
constructor(props) {
super(props)
this.state = {
error: '',
}
this._callBackend = this._callBackend.bind(this)
}
render() {
return (
<div>
<button onClick={this._callBackend}>Delete your city</button>
<InlineError error={this.state.error} />
</div>
)
}
_callBackend() {
this.setState({
error: '',
})
axios
.delete('/api/city')
.then(result => {
// сделать что-нибудь с результатом в том случае, если запрос оказался успешным
})
.catch(err => {
if (err.response.data.error === 'GENERIC') {
this.props.setError(err.response.data.description)
} else {
this.setState({
error: err.response.data.description,
})
}
})
}
}
export default SpecificErrorRequest
Тут стоит отметить, что для сброса специфических ошибок недостаточно, например, просто нажать на некую кнопку x
. То, что пользователь прочёл сообщение об ошибке и закрыл его, не помогает такую ошибку исправить. Исправить её можно, правильно сформировав запрос к серверу, например — введя в ситуации, показанной на предыдущем рисунке, имя города, который есть в базе. В результате очищать сообщение об ошибке имеет смысл, например, после выполнения нового запроса. Сбросить ошибку можно и в том случае, если пользователь внёс изменения в то, что будет использоваться при формировании нового запроса, то есть — при изменении содержимого поля ввода.
▍Ошибки, возникающие в клиентской части приложения
Как уже было сказано, для хранения данных о таких ошибках можно использовать состояние тех же компонентов, которое используется для хранения данных по специфическим ошибкам, поступающим с сервера. Предположим, мы позволяем пользователю отправить на сервер запрос на удаление города из базы только в том случае, если в соответствующем поле ввода есть какой-то текст. Отсутствие или наличие текста в поле можно проверить средствами клиентской части приложения.
В поле ничего нет, мы сообщаем об этом пользователю
Вот код файла SpecificErrorFrontend.js
, реализующий вышеописанный функционал.
import React, { Component } from 'react'
import axios from 'axios'
import InlineError from './InlineError'
class SpecificErrorRequest extends Component {
constructor(props) {
super(props)
this.state = {
error: '',
city: '',
}
this._callBackend = this._callBackend.bind(this)
this._changeCity = this._changeCity.bind(this)
}
render() {
return (
<div>
<input
type="text"
value={this.state.city}
style={{ marginRight: 15 }}
onChange={this._changeCity}
/>
<button onClick={this._callBackend}>Delete your city</button>
<InlineError error={this.state.error} />
</div>
)
}
_changeCity(e) {
this.setState({
error: '',
city: e.target.value,
})
}
_validate() {
if (!this.state.city.length) throw new Error('Please provide a city name.')
}
_callBackend() {
this.setState({
error: '',
})
try {
this._validate()
} catch (err) {
return this.setState({ error: err.message })
}
axios
.delete('/api/city')
.then(result => {
// сделать что-нибудь с результатом в том случае, если запрос оказался успешным
})
.catch(err => {
if (err.response.data.error === 'GENERIC') {
this.props.setError(err.response.data.description)
} else {
this.setState({
error: err.response.data.description,
})
}
})
}
}
export default SpecificErrorRequest
▍Интернационализация сообщений об ошибках с использованием кодов ошибок
Возможно, сейчас вы задаётесь вопросом о том, зачем нам нужны коды ошибок (наподобие GENERIC
), если мы показываем пользователю только сообщения об ошибках, полученных с сервера. Дело в том, что, по мере роста и развития приложения, оно, вполне возможно, выйдет на мировой рынок, а это означает, что настанет время, когда создателям приложения нужно будет задуматься о поддержке им нескольких языков. Коды ошибок позволяют отличать их друг от друга и выводить сообщения о них на языке пользователя сайта.
Итоги
Надеемся, теперь у вас сформировалось понимание того, как можно работать с ошибками в веб-приложениях. Нечто вроде console.error(err)
следует использовать только в отладочных целях, в продакшн подобные вещи, забытые программистом, проникать не должны. Упрощает решение задачи логирования использование какой-нибудь подходящей библиотеки наподобие loglevel.
Уважаемые читатели! Как вы обрабатываете ошибки в своих проектах?
When run javascript app integrating with some 3rd party library, in some cases , 3rd party lib will lead to bug , for example, following typical error:
Uncaught (in promise) TypeError: Cannot read property 'length' of undefined
The above error is ok to some extend because their code quality is not well(good code should always check if object exist,array length>0 etc before access them), what i concern is: i do not want the js app functions as if they crashed and not work anymore, even if the root cause which should cause above error disappear(for example, the array has been there now), the app will never survive to run again.
Is there some method to force js app to run even after 3rd party code fail(or my own code fail) but should SURVIVE to run for some other function area(which has no the crash causing there), or survive to work again if the cause for above exception disapper?
Please NOTE THAT, I DO NOT WANT TO TOUCH 3RD party code to let this work
Is there a generic mechanism in javascript to be switched on : just skip the un-caught error and continue to run as normal and NOT CRASH the app?
asked Oct 8, 2016 at 7:26
kidsitkidsit
2873 silver badges22 bronze badges
5
If you don’t want to use try-catch
process.on('uncaughtException', err => {
console.error('There was an uncaught error', err)
process.exit(1) //mandatory (as per the Node.js docs)
})
If you don’t exit the process, It can cause unexpected behaviour in your application which’s why it recommended.
Further reading
answered Mar 11, 2021 at 7:05
Gaurav SharmaGaurav Sharma
5831 gold badge4 silver badges26 bronze badges
1
As Nirus said, wrapping everything that could cause an error in a try…catch block:
try{
callYourLibHere();
}
Block should allow the code to ignore errors thrown, which will make the code continue to run after an error occurs. However, this will hide EVERY error inside this block. We can fix this by logging the errors into the console:
try {
callYourLibHere();
}
catch (e) {
console.log(e);
}
Please note that trying to solve the errors instead of ignoring them would be the better way to fix this.
answered Oct 8, 2016 at 8:15
1
Я предполагаю вызывать 3 файла javascript (jquery) следующим образом:
<script type="text/javascript" src="js/file1.js"></script>
<script type="text/javascript" src="js/file2.js"></script>
<script type="text/javascript" src="js/file3.js"></script>
В file2.js есть script или функция не работает, например, вызываемая функция не существует. Последствия, вызванные ошибкой в file2.js, — это скрипты в file3.js, не будут выполнены.
Есть ли способ script в file3.js еще выполняться? Можно обойти или игнорировать ошибку на файле2.js?
Спасибо.
Поделиться
Источник
6 ответов
Вы можете использовать блок catch try вокруг кода с ошибкой
try{
//code that causes an error
}catch(e){
functionToHandleError(e);
}
//continue from here
Но вы должны делать это только в том случае, если ошибка является известной проблемой/неизбежной проблемой с внешней библиотекой или происходит только при неправильном вводе пользователя. Вы не должны использовать его, чтобы программа могла работать любой ценой.
Некоторые способы избежать ошибок
- проверьте, существуют ли функции перед их вызовом
- проверьте, существует ли объект перед ссылкой на его свойства
- если пользовательский ввод задействован, проверьте вход, чтобы проверить, что это ожидаемый тип/диапазон/etc
- Если это проблема заказа на загрузку, рассмотрите переупорядочение ваших скриптов или, если это более крупное приложение, использующее систему управления зависимостями, такую как requireJS
- если объекты undefined после асинхронного запроса, помните, что код не ждет по асинхронному запросу, и он не будет определен до тех пор, пока не будет. Код, связанный с предоставленными значениями, должен выполняться в функции обратного вызова или с использованием дефредов /promises.
- Если вы ссылаетесь на элементы dom, подумайте о том, чтобы запустить JS-код из функции, которая запускается при загрузке DOM, например, jQuery document.ready или native window.onload.
- Если переменные javascript объявлены без ключевого слова
var
или привязаны к глобальному объекту, их можно изменить в других местах, кроме объявленных. Javascript также не имеет области блока. Убедитесь, что вы знаете все места, в которые изменяются ваши переменные, и попытайтесь ограничить область своих переменных, когда это возможно. Особенно избегайте использования слишком большого числа глобальных переменных для хранения состояния, так как может быть трудно узнать текущее состояние без многих проверок типа null/type.
Некоторые способы обработки ошибок
-
Если это ожидаемая проблема (IE, если вход может быть пустым и требует другой обработки), в идеале проверьте его с помощью инструкции if заблаговременно. Если вы не хотите этого делать, вы можете использовать try/catch для ввода ввода, запросить новый ввод или действовать на вход, указанный как соответствующий.
-
Если это непредвиденная проблема (IE вы не знаете, что происходит), блок catch может быть хорошим местом для вывода диагностической информации во время разработки. IE, не просто поймайте его и пусть он терпит неудачу, выведет сообщение, чтобы вы или другие разработчики знали, где он потерпел неудачу, а затем используйте ваши любимые инструменты веб-разработчика браузера для проверки значений в то время, выясните, что произошло, и добавьте обработку для этого случая, чтобы исправить это.
Что не делать
-
установите try/catch, а затем просто продолжайте работу программы. Если код, который работает неправильно, не нужен, избавитесь от него. Если это необходимо и работает неправильно, исправьте его.
-
использовать try catch в качестве проверки вашего ввода (IE, если функция имеет значение null, она выдает исключение, и я поймаю его, тогда я знаю, что функция равна нулю). По возможности проверяйте любые ожидаемые ошибки. Попытка/уловки должны обрабатывать «исключения», когда поведение необычно, и вам нужно сменить альтернативный путь кода или предупредить пользователя о том, что что-то не так.
Ben McCormick
Поделиться
Если у вас нет времени для отладки, попробуйте это:
function stoperror() {
return true;
}
Назовите его:
window.onerror = stoperror;
Функциональность очень проста: создайте функцию, которая всегда возвращает true, а затем всякий раз, когда возникает ошибка, вызовите эту функцию (возвращая true и подавляя ошибку).
mrj0909
Поделиться
вы можете спасти себя от «функции undefined», используя этот
//Simple function that will tell if the function is defined or not
function is_function(func) {
return typeof window[func] !== 'undefined' && $.isFunction(window[func]);
}
//usage
if (is_function("myFunction") {
alert("myFunction defined");
} else {
alert("myFunction not defined");
}
Nott Responding
Поделиться
вы можете использовать инструкции try и catch. И проверьте значение null, используя условие != null
try{
}catch(e){
}
PSR
Поделиться
Я бы предложил добавить нулевые проверки объектов (включая функции)
прежде чем предположить, что они существуют, так что код будет неудачным изящно.
Вот несколько идей:
Проверка Javascript, если функция существует
TGH
Поделиться
Оберните вызов, который приводит к ошибке в блоке try… catch:
try {
callNonExistentMethod();
}
catch (e) {
//Handle the error if you wish.
}
Justin Helgerson
Поделиться
Ещё вопросы
- 1ByteArrayOutputStream для шортов вместо байтов
- 1Автоматический слайдер JS?
- 1Запись из таблицы SQL Server CE в файл Excel
- 1Ведение журнала TimedRotatingFileHandler не работает правильно
- 1Как исправить зависание сборки PhoneGap .apk?
- 0Найдите поле ввода с определенным идентификатором и атрибутом обновления, используя jQuery
- 1Чтение сжатого файла NumPy во время «с open () как f»
- 0MySQL: mysqli_fetch_array ()
- 0застрял на велосипеде через несколько цветов.
- 1A * с осознанием врага
- 1Ошибка с чистой и встроенной в Netbeans
- 0взорвать и затем foreach данные не то же самое (ошибка php?)
- 0приложение для телефонной связи с «touchstart» все еще очень медленное
- 0Изменить текст в динамически создаваемом текстовом поле
- 0Создание представления в MySQL, которое должно обновляться еженедельно
- 0AngularJS: как искать текст на столе в угловых js?
- 1HashMap с определенным временем поиска?
- 1Изменение формы массива Python определенным образом
- 1Передача никогда не выполняется при остановке приложения
- 1Получить изменение состояния Интернета, когда Wi-Fi подключен
- 1Защита от фильтрации пустого объекта JS
- 1Исключительно сопоставление существующей базы данных с существующими POCO без конструктора или CodeFirst в Entity Framework 6
- 1Создайте файл jar, используя скомпилированные файлы классов и существующий файл MANIFEST.MF
- 0использовать DGEMM BLAS в Windows Eclipse
- 0Установка цвета границы с помощью jquery
- 1Как удалить выбранные строки из фрейма данных при условии
- 0Странное поведение Angular 1.3 при копировании элемента в NG-повтор
- 0Должен ли я использовать внешний ключ пользователя для всех таблиц?
- 0Javascript / JQuery сравнить входное значение с массивом
- 0Javascript / JQuery заменить текст удаляет форматирование Div
- 1Фильтр DataGridView скрывает отредактированные элементы
- 1Ошибка: тип программы уже существует: com.fasterxml.jackson.core.Base64Variant
- 1Собственный Android Inapp Покупка Где настроить лицензионный ключ RSA в кодировке Base64?
- 0Как динамически добавить тег <script> в template phpbb3?
- 0Как получить данные из двух таблиц со строками в виде столбцов? (динамический пивот в MySQL)
- 0Можно ли найти все посещенные ссылки в браузере?
- 0Изменяемая ручка блокирует перетаскиваемое взаимодействие
- 0Можно ли расширить имя переменной? PHP
- 1Хранение содержимого таблиц из одного класса в другой класс в Java
- 1Решить несколько запросов на повышение
- 0Угловой выбор помнить при перемещении по представлениям
- 0в GWT или GWTP, как экспортировать html-страницу, чтобы группа графического дизайна могла выполнять свою работу, не раскрывая им исходный код?
- 0Дизайн базы данных — много таблиц с уникальными тегами или одна таблица со всеми из них?
- 1Spring RequestMappping не работает
- 1TypeError: done не является функцией
- 0Печать общего дерева в формате дерева
- 1Модель не доступна вне пространства имен
- 0Пул объектов — создание объектов позже не работает
- 1Создать новые панды столбцы из нескольких столбцов
- 1API извлечения не возвращает такой же возврат XMLHttpRequest
Данная статья является переводом. Ссылка на оригинал.
В статье рассмотрим:
- Объект Error
- Try…catch
- Throw
- Call stack
- Наименование функций
- Парадигму асинхронного программирования Promise
Представьте, как разрабатываете RESTful web API на Node.js.
- Пользователи отправляют запросы к серверу для получения данных.
- Вопрос времени, когда в программу придут значения, которые не ожидались.
- В таком случае, когда программа получит эти значения, пользователи будут рады видеть подробное и конкретное описание ошибки.
- В случае когда нет соответствующего обработчика ошибки, отобразится стандартное сообщение об ошибке. Таким образом, пользователь увидит сообщение об ошибке «Невозможно выполнить запрос», что не несет полезной информации о том, что произошло.
- Поэтому стоит уделять внимание обработке ошибок, чтобы наша программа стала безопасной, отказоустойчивой, высокопроизводительной и без ошибок.
Анатомия Объекта Error
Первое, с чего стоит начать изучение – это объект Error.
Разберем на примере:
throw new Error('database failed to connect');
Здесь происходят две вещи: создается объект Error
и выбрасывается исключение.
Начнем с рассмотрения объекта Error
, и того, как он работает. К ключевому слову throw
вернемся чуть позже.
Объект Error
представляет из себя реализацию функции конструктора, которая использует набор инструкций (аргументы и само тело конструктора) для создания объекта.
Тем не менее, что же такое объекты ошибок? Почему они должны быть однородными? Это важные вопросы, поэтому давайте перейдем к ним.
Первым аргументом для объекта Error
является его описание.
Описание – это понятная человеку строка объекта ошибки. Также эта строка появляется в консоли, когда что-то пошло не так.
Объекты ошибок также имеют свойство name
, которое рассказывает о типе ошибки. Когда создается нативный объект ошибки, то свойство name
по умолчанию содержит Error. Вы также можете создать собственный тип ошибки, расширив нативный объект ошибки следующим образом:
class FancyError extends Error {
constructor(args){
super(args);
this.name = "FancyError"
}
}
console.log(new Error('A standard error'))
// { [Error: A standard error] }
console.log(new FancyError('An augmented error'))
// { [Your fancy error: An augmented error] name: 'FancyError' }
Обработка ошибок становится проще, когда у нас есть согласованность в объектах.
Ранее мы упоминали, что хотим, чтобы объекты ошибок были однородными. Это поможет обеспечить согласованность в объекте ошибки.
Теперь давайте поговорим о следующей части головоломки – throw
.
Ключевое слово Throw
Создание объектов ошибок – это не конец истории, а только подготовка ошибки к отправке. Отправка ошибки заключается в том, чтобы выбросить исключение. Но что значит выбросить? И что это значит для нашей программы?
Throw
делает две вещи: останавливает выполнение программы и находит зацепку, которая мешает выполнению программы.
Давайте рассмотрим эти идеи одну за другой:
- Когда JavaScript находит ключевое слово
throw
, первое, что он делает – предотвращает запуск любых других функций. Остановка снижает риск возникновения любых дальнейших ошибок и облегчает отладку программ. - Когда программа остановлена, JavaScript начнет отслеживать последовательную цепочку функций, которые были вызваны для достижения оператора
catch
. Такая цепочка называется стек вызовов (англ. call stack). Ближайшийcatch
, который находит JavaScript, является местом, где возникает выброшенное исключение. Если операторыtry/catch
не найдены, тогда возникает исключение, и процесс Node.js завершиться, что приведет к перезапуску сервера.
Бросаем исключения на примере
Мы рассмотрели теорию, а теперь давайте изучим пример:
function doAthing() {
byDoingSomethingElse();
}
function byDoingSomethingElse() {
throw new Error('Uh oh!');
}
function init() {
try {
doAthing();
} catch(e) {
console.log(e);
// [Error: Uh oh!]
}
}
init();
Здесь в функции инициализации init()
предусмотрена обработка ошибок, поскольку она содержит try/catch
блок.
init()
вызывает функцию doAthing()
, которая вызывает функцию byDoingSomethingElse()
, где выбрасывается исключение. Именно в этот момент ошибки, программа останавливается и начинает отслеживать функцию, вызвавшую ошибку. Далее в функции init()
и выполняет оператор catch
. С помощью оператора catch
мы решаем что делать: подавить ошибку или даже выдать другую ошибку (для распространения вверх).
Стек вызовов
То, что показано в приведенном выше примере – это проработанный пример стека вызовов. Как и большинство языков, JavaScript использует концепцию, известную как стек вызовов.
Но как работает стек вызовов?
Всякий раз, когда вызывается функция, она помещается в стек, а при завершении удаляется из стека. Именно от этого стека мы получили название «трассировки стека».
Трассировка стека – это список функций, которые были вызваны до момента, когда в программе произошло исключение.
Она часто выглядит так:
Error: Uh oh!
at byDoingSomethingElse (/filesystem/aProgram.js:7:11)
at doAthing (/filesystem/aProgram.js:3:5)
at init (/filesystem/aProgram.js:12:9)
at Object.<anonymous> (/filesystem/aProgram.js:19:1)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
На этом этапе вам может быть интересно, как стек вызовов помогает нам с обработкой ошибок Node.js. Давайте поговорим о важности стеков вызовов.
Стек вызовов предоставляет «хлебные крошки», помогая проследить путь, который привел к исключению(ошибке).
Почему у нас должны быть функции без имен? Иногда в наших программах мы хотим определить маленькие одноразовые функции, которые выполняют небольшую задачу. Мы не хотим утруждать себя задачей давать им имена, но именно эти анонимные функции могут вызвать у нас всевозможные головные боли. Анонимная функция удаляет имя функции из нашего стека вызовов, что делает наш стек вызовов значительно более сложным в использовании.
Обратите внимание, что присвоить имена функциям в JavaScript не так просто. Итак, давайте кратко рассмотрим различные способы определения функций, и рассмотрим некоторые ловушки в именовании функций.
Как называть функции
Чтобы понять, как называть функции, давайте рассмотрим несколько примеров:
// анонимная функция
const one = () => {};
// анонимная функция
const two = function () {};
// функция с явным названием
const three = function explicitFunction() {};
Вот три примера функций.
Первая – это лямбда (или стрелочная функция). Лямбда функции по своей природе анонимны. Не запутайтесь. Имя переменной one
не является именем функции. Имя функции следующее за ключевым словом function
необязательно. Но в этом примере мы вообще ничего не передаем, поэтому наша функция анонимна.
Примечание
Не помогает и то, что некоторые среды выполнения JavaScript, такие как V8, могут иногда угадывать имя вашей функции. Это происходит, даже если вы его не даете.
Во втором примере мы получили функциональное выражение. Это очень похоже на первый пример. Это анонимная функция, но просто объявленная с помощью ключевого слова function
вместо синтаксиса жирной стрелки.
В последнем примере объявление переменной с подходящим именем explicitFunction
. Это показывает, что это единственная функция, у которой соответствующее имя.
Как правило, рекомендуется указывать это имя везде, где это возможно, чтобы иметь более удобочитаемую трассировку стека.
Обработка асинхронных исключений
Мы познакомились с объектом ошибок, ключевым словом throw
, стеком вызовов и наименованием функций. Итак, давайте обратим наше внимание на любопытный случай обработки асинхронных ошибок. Почему? Потому что асинхронный код ведет себя не так, как ожидаем. Асинхронное программирование необходимо каждому программисту на Node.js.
Javascript – это однопоточный язык программирования, а это значит, что Javascript запускается с использованием одного процессора. Из этого следует, что у нас есть блокирующий и неблокирующий код. Блокирующий код относится к тому, будет ли ваша программа ожидать завершения асинхронной задачи, прежде чем делать что-либо еще. В то время как неблокирующий код относится к тому, где вы регистрируете обратный вызов (callback) для выполнения после завершения задачи.
Стоит упомянуть, что есть два основных способа обработки асинхронности в JavaScript: promises (обещания или промисы) и callback (функция обратного вызова). Мы намеренно игнорируем async/wait
, чтобы избежать путаницы, потому что это просто сахар поверх промисов.
В статье мы сфокусируемся на промисах. Существует консенсус в отношении того, что для приложений промисы превосходят обратные вызовы с точки зрения стиля программирования и эффективности. Поэтому в этой статье проигнорируем подход с callback-ами, и предположим, что вместо него вы выберете promises.
Примечание
Существует множество способов конвертировать код на основе callback-ов в promises. Например, вы можете использовать такую утилиту, как promisify, или обернуть свои обратные вызовы в промисы, например, так:
var request = require('request'); //http wrapped module
function requestWrapper(url, callback) {
request.get(url, function (err, response) {
if (err) {
callback(err);
} else {
callback(null, response);
}
})
}
Мы разберемся с этой ошибкой, обещаю!
Давайте взглянем на анатомию обещаний.
Промисы в JavaScript – это объект, представляющий будущее значение. Promise API позволяют нам моделировать асинхронный код так же, как и синхронный. Также стоит отметить, что обещание обычно идет в цепочке, где выполняется одно действие, затем другое и так далее.
Но что все это значит для обработки ошибок Node.js?
Промисы элегантно обрабатывают ошибки и перехватывают любые ошибки, которые им предшествовали в цепочке. С помощью одного обработчика обрабатывается множество ошибок во многих функциях.
Изучим код ниже:
function getData() {
return Promise.resolve('Do some stuff');
}
function changeDataFormat() {
// ...
}
function storeData(){
// ...
}
getData()
.then(changeDataFormat)
.then(storeData)
.catch((e) => {
// Handle the error!
})
Здесь видно, как объединить обработку ошибок для трех различных функций в один обработчик, т. е. код ведет себя так же, как если бы три функции заключались в синхронный блок try/catch
.
Отлавливать или не отлавливать?
На данном этапе стоит спросить себя, повсеместно ли добавляется .catch
к промисам, поскольку это опционально. Из-за проблем с сетью, аппаратного сбоя или истекшего времени ожидания в асинхронных вызовах возникает исключение. По этим причинам указывайте программе, что делать в случаях невыполнения промиса.
Запомните «Золотое правило» – каждый раз обрабатывать исключения в обещаниях.
Риски асинхронного try/catch
Мы приближаемся к концу в нашем путешествии по обработке ошибок в Node.js. Пришло время поговорить о ловушках асинхронного кода и оператора try/catch
.
Вам может быть интересно, почему промис предоставляет метод catch
, и почему мы не можем просто обернуть нашу реализацию промиса в try/catch
. Если бы вы сделали это, то результаты были бы не такими, как вы ожидаете.
Рассмотрим на примере:
try {
throw new Error();
} catch(e) {
console.log(e); // [Error]
}
try {
setTimeout(() => {
throw new Error();
}, 0);
} catch(e) {
console.log(e); // Nothing, nada, zero, zilch, not even a sound
}
try/catch
по умолчанию синхронны, что означает, что если асинхронная функция выдает ошибку в синхронном блоке try/catch
, ошибка не будет брошена.
Однозначно это не то, что ожидаем.
***
Подведем итог! Необходимо использовать обработчик промисов, когда мы имеем дело с асинхронным кодом, а в случае с синхронным кодом подойдет try/catch
.
Заключение
Из этой статьи мы узнали:
- как устроен объект Error;
- научились создавать свои собственные ошибки;
- как работает стек вызовов;
- практики наименования функций, для удобочитаемой трассировки стека;
- как обрабатывать асинхронные исключения.
***
Материалы по теме
- 🗄️ 4 базовых функции для работы с файлами в Node.js
- Цикл событий: как выполняется асинхронный JavaScript-код в Node.js
- Обработка миллионов строк данных потоками на Node.js