Устранение основных ошибок в JavaScript
JavaScript — это язык программирования, который используется во фронтенд- и бэкенд-разработке. Однако иногда правильно расшифровать сообщение об ошибке в JavaScript бывает немного сложно. При устранении проблем в приложени очень важно понимание того, на что ссылается сообщение об ошибке. Современные браузеры поставляются со встроенными утилитами отладки, которые помогают в этом. Каждый браузер по-разному визуально выводит сообщения об ошибках. Но эти сообщения подскажут вам, что происходит с кодом JavaScript.
В этом мануале мы рассмотрим три распространенных типах ошибок JavaScript, которые возникают в среде браузера: ReferenceError, SyntaxError и TypeError. Чтобы следовать этому туториалу, вы должны иметь представление о JavaScript и консоли разработчика.
Примеры в этом туториале помогут вам ознакомиться с распространенными сообщениями об ошибках. Сообщения в примерах из браузера Chrome, в Firefox или Edge могут содержать немного другую информацию, но типы ошибок одинаковы для всех.
Типы ошибок JavaScript
Ошибки в JavaScript основаны на объекте Error . Это встроенный объект, который содержит информацию о типе возникшей ошибки, за которой следует сообщение с подробным описанием возможной причины. Например, вы можете столкнуться со следующей ошибкой:
VM170:1 Uncaught ReferenceError: shark is not defined at :1:1
Если разобрать это сообщение, вы узнаете, что ReferenceError — это тип обнаруженной ошибки. После двоеточия следует сообщение, которое описывает ошибку: shark is not defined. Последняя строка в этом сообщении указывает, где именно произошла ошибка в коде: 1:1.
Каждый раз, когда браузер обнаруживает ошибку, тип и сообщение об ошибке указывают на проблему. С помощью этой информации можно определить, как отладить код на основе типа ошибки и сообщения, которое вы получите.
Ошибка ReferenceError
ReferenceError возникает, когда вы пытаетесь получить доступ к переменной, которую вы еще не создали. Она также возникает, когда вы вызываете переменную до ее инициализации.
Неопределенные переменные
Например, если вы неправильно написали название переменной при выполнении console.log(), вы получите ошибку ReferenceError:
let sammy = 'A Shark dreaming of the cloud.'; console.log(sammmy);
Вывод будет следующим:
Uncaught ReferenceError: sammmy is not defined at :1:13
Переменная sammmy с тремя буквами m не существует и не определена. Чтобы исправить эту ошибку, можно обновить переменную и написать ее правильно,а затем повторно выполнить команду. Если все прошло успешно, то вы не получите сообщение об ошибке. В целом рекомендуется всегда заранее проверять код на наличие опечаток, чтобы избежать ошибок с неопределенными переменными.
Доступ к переменной до ее объявления
Также вы можете столкнуться с ошибкой при попытке получения доступа к переменной до ее объявления в коде:
function sharkName() console.log(shark); let shark = 'sammy'; > VM223:2 Uncaught ReferenceError: Cannot access 'shark' before initialization at sharkName (:2:17) at :1:1
В этом примере при выполнении console.log(shark) переменная shark объявлена после вызова, что приводит к ошибке. Как вы сами знаете, сначала нужно объявить переменную, а уже потом пытаться получить к ней доступ.
Примечание: из-за того, как работают объявления let и const, объявления переменных всплывают, но в предыдущем примере они не доступны, потому что находятся в так называемой “ временной мертвой зоне ” (Temporal Dead Zone). Чтобы избежать этого, рекомендуется объявлять переменные let и const в начале области.
Чтобы исправить эту ошибку, объявите переменную shark перед выполнением команды console.log():
function sharkName() let shark = 'sammy'; console.log(shark); >
Ошибка ReferenceError часто связана с переменными и областью видимости. Это выходит за рамки данного мануала.
Ошибка SyntaxError
Когда интерпретатор JavaScript просматривает код, он может выдать ошибку SyntaxError, если обнаружит код, который не соответствует спецификации языка. В этом случае ваш код прекратит выполнение и вы получите сообщение о синтаксической ошибке.
Отсутствует закрывающий знак
Например, когда вы пишете функцию и забываете закрыть круглую скобку, ), чтобы заключить свой код, вы получите SyntaxError с очень конкретным сообщением об отсутствующем элементе:
function sammy(animal) if(animal == 'shark') return `I'm cool`; > else return `You're cool`; > > sammy('shark';
Uncaught SyntaxError: missing ) after argument list
В сообщении об ошибке указывается отсутствующий символ в коде. В этом примере в вызове функции sammy отсутствует закрывающая скобка ):
. . . sammy('shark');
Отсутствие завершающей фигурной скобки > в конце функции или квадратной ] в массиве также может привести к этой ошибке. Убедитесь, что вы правильно закрываете функции, массивы и объекты.
Одинаковые имена переменных
Вы можете столкнуться с этой ошибкой, когда одно имя используется как параметр функции и внутри ее тела. Рассмотрим следующий пример:
function sammy(animal) let animal = 'shark'; >
VM132:2 Uncaught SyntaxError: Identifier 'animal' has already been declared
Чтобы исправить эту ошибку, убедитесь, что вы используете уникальные и конкретные имена переменных внутри тела функции. Объявив новое имя переменной, например, animalType, вы устраните конфликт между параметром функции и переменной let в теле:
function sammy(animal) let animalType = 'shark'; >
Если в теле функции вы собираетесь изменять параметр, не объявляйте переменную с таким же именем. Например, вы можете удалить объявление let внутри тела функции:
function sammy(animal) animal = 'shark'; >
Убедитесь, что при работе с переменными внутри и вне тела функции вы даете им уникальные имена. При доступе к параметрам функции вы можете использовать их в теле без объявления переменной, например, без let.
Случайные символы
Иногда в коде встречаются опечатки: к примеру, пропущенные символы (как мы уже видели выше) или ненужные повторения символов. Посмотрите на следующий код:
let sharkCount = 0; function sammy() sharkCount+; console.log(sharkCount); >
Uncaught SyntaxError: Unexpected token ';'
Дополнительным элементом в этом примере является знак плюс (+) после sharkCount внутри тела функции:
. . . function sammy() sharkCount++; console.log(sharkCount); >
При возникновении ошибки SyntaxError: Unexpected token проверьте код на наличие пропущенных или дополнительных операторов, таких как знак плюс (+) в примере.
Ошибка TypeError
TypeError возникает, когда значение функции или переменной имеет непредвиденный тип.
Методы массивов и объекты
Одна из распространенных ошибок — использование метода массива для итерации объекта. Например, перебрать объект с помощью метода .map() нельзя, потому что он доступен только для массивов:
const sharks = shark1: 'sammy', shark2: 'shelly', shark3: 'sheldon' > sharks.map((shark) => `Hello there $!`);
Uncaught TypeError: sharks.map is not a function at :1:8
Один из вариантов исправления предыдущего примера — использовать цикл for…in, который работает с объектами:
const sharks = shark1: 'sammy', shark2: 'shelly', shark3: 'sheldon' > for (let key in sharks) console.log(`Hello there $!`); >
Также можно преобразовать объект sharks в массив, чтобы использовать метод .map():
const sharks = ['sammy', 'shelly', 'sheldon']; sharks.map((shark) => `Hello there $!`);
Когда вы работаете с разными массивами и объектами, методы легко перепутать. Убедитесь, что метод соответствует типу данных, с которыми вы работаете.
Правильные методы деструктуризации
Аналогично, попытка выполнить итерацию над объектом с деструктуризацией массива приведет к ошибке TypeError:
const shark = name: 'sammy', age: 12, cloudPlatform: 'sammy' > const [name, age, cloudPlatform] = sharks;
VM23:7 Uncaught TypeError: sharks is not iterable at :7:26
Один из способов решить эту проблему — использовать деструктуризацию объекта, чтобы создавать новые переменные на основе ключей объекта:
const shark = name: 'sammy', age: 12, cloudPlatform: 'sammy' > const = shark; console.log(name);
Получим следующий вывод:
sammy
В зависимости от того, как вы структурируете данные, — с помощью массивов или объектов, — убедитесь, что вы используете соответствующие методы для извлечения значений.
Подводим итоги
В этом туториале мы рассмотрели три распространенных типа ошибок JavaScript и некоторые связанные с ними сообщения, а также обсудили, как отладить эти проблемы. Конечно, этот краткий материал сложно назвать исчерпывающим, но вы получили представление о том, как JavaScript кооперируется с браузерами, чтобы указать на проблемы в коде.
Читайте также:
- Методы объектов в JavaScript
- Методы массивов в JavaScript: методы итерации
- Отладка JavaScript с помощью Visual Studio Code и DevTools от Google Chrome
How to resolve this «at Object. » error in jest.js
I have lots of global objects and say a function which is to be tested but when I run npm test i get at Object. (index.js:2:1) at Object. (index.test.js:1:1) this error this error points at the object «.» of gblob.aobj=<>; for simplicity sake i have created a index.js and index.test.js file ,im new to jest.js note: im working on SPA: single page application where this variable is created in another js index.js
gblob.aobj=<>; function sum(a, b) < return a + b; >module.exports = sum;
«»» «»» index.test.js
const sum = require('./index'); test('adds 1 + 2 to equal 3', () => < expect(a.hy()).toBe(3); >);
«»» «»» package.json if i need to add something here
< "name": "my-app", "version": "1.0.0", "description": "", "main": "index.js", "scripts": < "test": "jest" >, " author": "", "license": "ISC", "devDependencies": < "jest": "^24.7.1" >>
Как найти ошибки в коде
Начните с просмотра видео, чтобы познакомиться с принципами эффективной отладки и избежать распространенных ошибок.
Также загляните в наш Твиттер. В одном треде мы разобрали несколько примеров, когда вроде бы рабочий код не проходит тесты на Хекслете. Сам тред можно найти здесь.
Примеры в этой статье написаны на языке JavaScript, но принципы одинаковы для любого языка.
Тесты
Код на Хекслете проверяется с помощью автоматических тестов. Обычно они написаны на том же языке, на котором написан сам код. Общий принцип работы такого вида тестирования довольно прост. Тестируемая программа загружается в память и вызывается с разными параметрами, а тесты следят за тем, чтобы ее поведение соответствовало ожидаемому.
Когда код не проходит тесты, то обычно говорят что тесты упали. В этот момент начинается самое интересное. Необходимо понять, где и почему возникла ошибка. И вывод тестов в этом процессе играет ключевую роль, это главный помощник и проводник. Но необходим опыт, чтобы начать делать правильные выводы из того, что пишут тесты.
В первую очередь нужно классифицировать проблему. Ошибки в тестах можно грубо разделить на две категории:
- ошибки, которые выдает компилятор или интерпретатор: синтаксическая ошибка, ошибка типизации
- ошибочные утверждения.
Утверждения
Утверждение — это специальная функция, которая вызывает ваш код с определенными параметрами и проверяет, что он возвращает ожидаемый результат. Например:
assert(isPrime(3)); assert.equal(factorial(3), 6);
Самое важное: если тесты упали на утверждении, это означает, что ваш код как минимум отработал, но его результат не соответствует ожидаемому. Причем часто бывает так, что часть утверждений проходит проверку, то есть код возвращает правильный результат, а часть — нет, обычно в пограничных случаях. В конечном итоге падение теста на утверждении говорит о том, что в коде логическая ошибка.
Ниже — пример вывода упавшего теста. То, насколько вывод подробный, зависит от вида утверждения и возможностей тестовой среды.
assert.js:89 throw new assert.AssertionError( < ^ AssertionError: 3 == 1 at Object.(test.js:4:8) at Module._compile (module.js:413:34) at loader (/usr/local/lib/node_modules/babel-register/lib/node.js:126:5) at Object.require.extensions.(anonymous function) [as .js] (/usr/local/lib/node_modules/babel-register/lib/node.js:136:7) at Module.load (module.js:357:32) at Function.Module._load (module.js:314:12) at Function.Module.runMain (module.js:447:10) at /usr/local/lib/node_modules/babel-cli/lib/_babel-node.js:161:27 at Object. (/usr/local/lib/node_modules/babel-cli/lib/_babel-node.js:162:7) at Module._compile (module.js:413:34)
Вывод можно разделить на две части:
- Первая — описание того, что ожидалось от функции и что было получено. В нашем примере это строка AssertionError: 3 == 1 . Читается она следующим образом: «ожидалось, что функция вернет 3, но она вернула 1». Это уже хорошо, но еще хотелось бы увидеть, с какими параметрами была вызвана функция. И в этом нам поможет вторая часть вывода.
- Вторая часть называется backtrace, она содержит список функций, которые последовательно вызывались в коде. Порядок вывода, чаще всего, обратный: в начале то, что вызывалось последним.
В первую очередь нужно, начиная с конца, найти первое упоминание функции из файла, который похож на тестовый. Обычно его называние содержит слово test. В примере выше это at Object. (test.js:4:8) . В скобках указана строчка, на которой находится вызов этого утверждения. В данном случае — строчка 4.
Всё, что теперь остается, это зайти в соответствующий файл и посмотреть то, как вызывалась ваша функция.
Предупреждения компилятора и интерпретатора
Синтаксические ошибки
Самый простой тип ошибок. Такая ошибка говорит о том, что вы ошиблись в синтаксисе. Забыли запятую, скобку и тому подобные вещи. Такие ошибки легко находить и исправлять. Синтаксическая ошибка сопровождается текстом, по которому можно загуглить возможные причины.
Другие ошибки
Большой класс ошибок, которые могут возникать в процессе разработки. В выводе компилятора или интерпретатора всегда присутствует сообщение об ошибке, которое очень важно понять. Проще всего сделать, загуглив текст ошибки. Рекомендуем наш гайд Как искать техническую информацию.
Также ошибки содержат вывод backtrace, по которому можно найти то место, в котором возникла ошибка и попробовать его проанализировать.
Многие из этих ошибок легко исправить с помощью отладочной печати (см. урок Отладочная печать).
Последнее обновление более двух недель назад
Промисы: обработка ошибок
Цепочки промисов отлично подходят для перехвата ошибок. Если промис завершается с ошибкой, то управление переходит в ближайший обработчик ошибок. На практике это очень удобно.
Например, в представленном ниже примере для fetch указана неправильная ссылка (сайт не существует), и .catch перехватывает ошибку:
fetch('https://no-such-server.blabla') // ошибка .then(response => response.json()) .catch(err => alert(err)) // TypeError: failed to fetch (текст может отличаться)
Как видно, .catch не обязательно должен быть сразу после ошибки, он может быть далее, после одного или даже нескольких .then
Или, может быть, с сервером всё в порядке, но в ответе мы получим некорректный JSON. Самый лёгкий путь перехватить все ошибки – это добавить .catch в конец цепочки:
fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => fetch(`https://api.github.com/users/$`)) .then(response => response.json()) .then(githubUser => new Promise((resolve, reject) => < let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() =>< img.remove(); resolve(githubUser); >, 3000); >)) .catch(error => alert(error.message));
Если все в порядке, то такой .catch вообще не выполнится. Но если любой из промисов будет отклонён (проблемы с сетью или некорректная json-строка, или что угодно другое), то ошибка будет перехвачена.
Неявный try…catch
Вокруг функции промиса и обработчиков находится "невидимый try..catch ". Если происходит исключение, то оно перехватывается, и промис считается отклонённым с этой ошибкой.
Например, этот код:
new Promise((resolve, reject) => < throw new Error("Ошибка!"); >).catch(alert); // Error: Ошибка!
…Работает так же, как и этот:
new Promise((resolve, reject) => < reject(new Error("Ошибка!")); >).catch(alert); // Error: Ошибка!
"Невидимый try..catch " вокруг промиса автоматически перехватывает ошибку и превращает её в отклонённый промис.
Это работает не только в функции промиса, но и в обработчиках. Если мы бросим ошибку ( throw ) из обработчика ( .then ), то промис будет считаться отклонённым, и управление перейдёт к ближайшему обработчику ошибок.
new Promise((resolve, reject) => < resolve("ок"); >).then((result) => < throw new Error("Ошибка!"); // генерируем ошибку >).catch(alert); // Error: Ошибка!
Это происходит для всех ошибок, не только для тех, которые вызваны оператором throw . Например, программная ошибка:
new Promise((resolve, reject) => < resolve("ок"); >).then((result) => < blabla(); // нет такой функции >).catch(alert); // ReferenceError: blabla is not defined
Финальный .catch перехватывает как промисы, в которых вызван reject , так и случайные ошибки в обработчиках.
Пробрасывание ошибок
Как мы уже заметили, .catch ведёт себя как try..catch . Мы можем иметь столько обработчиков .then , сколько мы хотим, и затем использовать один .catch в конце, чтобы перехватить ошибки из всех обработчиков.
В обычном try..catch мы можем проанализировать ошибку и повторно пробросить дальше, если не можем её обработать. То же самое возможно для промисов.
Если мы пробросим ( throw ) ошибку внутри блока .catch , то управление перейдёт к следующему ближайшему обработчику ошибок. А если мы обработаем ошибку и завершим работу обработчика нормально, то продолжит работу ближайший успешный обработчик .then .
В примере ниже .catch успешно обрабатывает ошибку:
// the execution: catch -> then new Promise((resolve, reject) => < throw new Error("Ошибка!"); >).catch(function(error) < alert("Ошибка обработана, продолжить работу"); >).then(() => alert("Управление перейдёт в следующий then"));
Здесь блок .catch завершается нормально. Поэтому вызывается следующий успешный обработчик .then .
В примере ниже мы видим другую ситуацию с блоком .catch . Обработчик (*) перехватывает ошибку и не может обработать её (например, он знает как обработать только URIError ), поэтому ошибка пробрасывается далее:
// the execution: catch -> catch -> then new Promise((resolve, reject) => < throw new Error("Ошибка!"); >).catch(function(error) < // (*) if (error instanceof URIError) < // обрабатываем ошибку >else < alert("Не могу обработать ошибку"); throw error; // пробрасывает эту или другую ошибку в следующий catch >>).then(function() < /* не выполнится */ >).catch(error => < // (**) alert(`Неизвестная ошибка: $`); // ничего не возвращаем => выполнение продолжается в нормальном режиме >);
Управление переходит от первого блока .catch (*) к следующему (**) , вниз по цепочке.
Необработанные ошибки
Что произойдёт, если ошибка не будет обработана? Например, мы просто забыли добавить .catch в конец цепочки, как здесь:
new Promise(function() < noSuchFunction(); // Ошибка (нет такой функции) >) .then(() => < // обработчики .then, один или более >); // без .catch в самом конце!
В случае ошибки выполнение должно перейти к ближайшему обработчику ошибок. Но в примере выше нет никакого обработчика. Поэтому ошибка как бы «застревает», её некому обработать.
На практике, как и при обычных необработанных ошибках в коде, это означает, что что-то пошло сильно не так.
Что происходит, когда обычная ошибка не перехвачена try..catch ? Скрипт умирает с сообщением в консоли. Похожее происходит и в случае необработанной ошибки промиса.
JavaScript-движок отслеживает такие ситуации и генерирует в этом случае глобальную ошибку. Вы можете увидеть её в консоли, если запустите пример выше.
В браузере мы можем поймать такие ошибки, используя событие unhandledrejection :
window.addEventListener('unhandledrejection', function(event) < // объект события имеет два специальных свойства: alert(event.promise); // [object Promise] - промис, который сгенерировал ошибку alert(event.reason); // Error: Ошибка! - объект ошибки, которая не была обработана >); new Promise(function() < throw new Error("Ошибка!"); >); // нет обработчика ошибок
Это событие является частью стандарта HTML.
Если происходит ошибка, и отсутствует её обработчик, то генерируется событие unhandledrejection , и соответствующий объект event содержит информацию об ошибке.
Обычно такие ошибки неустранимы, поэтому лучше всего – информировать пользователя о проблеме и, возможно, отправить информацию об ошибке на сервер.
В не-браузерных средах, таких как Node.js, есть другие способы отслеживания необработанных ошибок.
Итого
- .catch перехватывает все виды ошибок в промисах: будь то вызов reject() или ошибка, брошенная в обработчике при помощи throw .
- .then также перехватывает ошибки таким же образом, если задан второй аргумент (который является обработчиком ошибок).
- Необходимо размещать .catch там, где мы хотим обработать ошибки и знаем, как это сделать. Обработчик может проанализировать ошибку (могут быть полезны пользовательские классы ошибок) и пробросить её, если ничего не знает о ней (возможно, это программная ошибка).
- Можно и совсем не использовать .catch , если нет нормального способа восстановиться после ошибки.
- В любом случае нам следует использовать обработчик события unhandledrejection (для браузеров и аналог для других окружений), чтобы отслеживать необработанные ошибки и информировать о них пользователя (и, возможно, наш сервер), благодаря чему наше приложение никогда не будет «просто умирать».