Uncaught TypeError: не удаётся прочитать свойство undefined - что означает эта ошибка JavaScript
Cannot read properties of undefined - ошибка, которую поначалу легко неправильно понять.
Например, браузер показывает:
//text
Uncaught TypeError: Cannot read properties of undefined (reading 'name')А в коде у нас всего одна строка:
//javascript
console.log(user.name);Первым делом обычно смотришь на name: существует ли такое свойство, правильно ли оно написано, не опечатался ли я.
Но здесь причина находится в другом месте.
Если user в момент выполнения равен undefined, браузер фактически пытается сделать вот это:
//javascript
undefined.nameОтсюда и TypeError.
Причём user совсем не обязательно был изначально undefined. Он мог прийти таким из API, получиться из несуществующего элемента массива или оказаться результатом функции, которая ничего не вернула.
Поэтому я обычно смотрю на выражение слева направо и ищу место, где значение стало undefined.
Самый простой пример
Представим такой код:
//javascript
const user = undefined;
console.log(user.name);При запуске браузер выдаст:
//text
Uncaught TypeError: Cannot read properties of undefined (reading 'name')Почему?
Потому что JavaScript фактически пытается выполнить:
//javascript
undefined.nameА у undefined нет никаких свойств.
Это важно понять именно на этом уровне. Ошибка не означает:
«Свойства
nameне существует».
Она означает:
«Я пытаюсь прочитать
name, но значение слева от точки оказалосьundefined».
Разница небольшая на словах, но при отладке она решает почти всё.
Где обычно появляется такая ошибка
Самый неприятный вариант - когда код выглядит совершенно нормально.
Например:
//javascript
const users = [
{ name: 'Антон' },
{ name: 'Иван' }
];
console.log(users[2].name);На первый взгляд всё выглядит логично. У объектов есть name.
Но users[2] не существует.
Массив содержит элементы с индексами 0 и 1, а запрос к users[2] возвращает:
//javascript
undefinedПоэтому следующая часть:
//javascript
users[2].nameпревращается в:
//javascript
undefined.nameИ появляется ошибка.
Вот почему при такой проблеме полезно смотреть не на последнее свойство после точки, а на всё выражение целиком.
Ошибка часто находится на шаг раньше
Это один из самых полезных способов читать такое сообщение.
Допустим, есть:
//javascript
console.log(response.user.name);Здесь JavaScript последовательно обращается к нескольким значениям:
//text
response
↓
user
↓
nameСначала он получает response, затем пытается найти у него свойство user, а уже после этого - name.
Если:
//javascript
response.userравен undefined, то следующая попытка:
//javascript
response.user.nameприведёт к TypeError.
Поэтому при такой ошибке полезно проверить значение на предыдущем шаге:
//javascript
console.log(response);
console.log(response.user);Если второй console.log показывает:
//text
undefinedпричина уже понятна: проблема возникла при получении user, а не при чтении самого name.
Особенно часто это происходит с данными от API
Например, разработчик ожидает такой ответ сервера:
//json
{
"user": {
"name": "Stanly"
}
}И пишет:
//javascript
console.log(data.user.name);Но сервер по какой-то причине вернул:
//json
{
"name": "Stanly"
}Теперь:
//javascript
data.userравно undefined.
И выражение:
//javascript
data.user.nameпадает с ошибкой.
Причём API может работать совершенно нормально. Сервер отвечает, HTTP-запрос успешен, JSON корректный.
Проблема только в том, что структура полученных данных отличается от той, которую ожидал JavaScript-код.
Это довольно частая ситуация при работе с API, особенно когда формат ответа меняется или разные endpoint'ы возвращают разные структуры.
Ещё одна классическая причина - данные появляются позже
Например:
//javascript
let user;
fetch('/api/user')
.then(response => response.json())
.then(data => {
user = data;
});
console.log(user.name);Здесь легко подумать: «Но ведь user заполнится».
Да. Только позже.
fetch() выполняется асинхронно. Когда интерпретатор доходит до:
//javascript
console.log(user.name);запрос ещё может не закончиться.
В этот момент:
//javascript
user === undefinedПоэтому JavaScript и сообщает об ошибке.
Правильнее обращаться к данным после их получения:
//javascript
fetch('/api/user')
.then(response => response.json())
.then(data => {
console.log(data.name);
});Или использовать async/await:
//javascript
async function loadUser() {
const response = await fetch('/api/user');
const data = await response.json();
console.log(data.name);
}Здесь уже понятен порядок: сначала получить данные, потом использовать их.
Иногда проблема вообще в функции
Есть ещё один случай, который часто заставляет искать ошибку не там.
//javascript
function getUser() {
// ничего не возвращает
}
const user = getUser();
console.log(user.name);Переменная user здесь тоже будет undefined.
Функция существует. Она вызывается. Никакой ошибки при вызове нет.
Но функция ничего не вернула.
В JavaScript функция без return возвращает undefined.
Например:
//javascript
function getUser() {
const user = {
name: 'Антон'
};
}Разработчик может ожидать, что getUser() вернёт объект.
Но для этого нужен:
//javascript
function getUser() {
return {
name: 'Антон'
};
}Теперь:
//javascript
const user = getUser();
console.log(user.name);работает.
А если ошибка появляется внутри цепочки?
Вот такой код встречается довольно часто:
//javascript
const city = users[0].address.city;Здесь потенциально несколько мест, где может отсутствовать значение.
Например:
//javascript
usersможет быть undefined.
Или:
//javascript
users[0]может быть undefined.
Или:
//javascript
users[0].addressможет быть undefined.
Поэтому полезно временно разобрать выражение:
//javascript
const user = users[0];
console.log(user);
const address = user.address;
console.log(address);
console.log(address.city);Так место проблемы находится гораздо быстрее.
Более длинные цепочки
Та же логика работает с более глубокими объектами.
Например:
//javascript
const city = data.user.profile.name;Здесь потенциально проблема может возникнуть на нескольких уровнях:
//text
data
↓
user
↓
profile
↓
nameНапример, если:
//javascript
data.userравен undefined, JavaScript не сможет перейти к:
//javascript
data.user.profileЕсли data.user существует, но:
//javascript
data.user.profileравен undefined, ошибка возникнет уже при попытке получить name.
Поэтому длинную цепочку удобно временно разобрать:
//javascript
console.log(data);
console.log(data.user);
console.log(data.user?.profile);Так можно быстро определить, на каком именно уровне пропало ожидаемое значение.
Можно ли просто поставить optional chaining?
Иногда - да.
Например:
//javascript
console.log(user?.name);Если user равен null или undefined, optional chaining остановит дальнейший доступ к свойству и вернёт undefined вместо TypeError.
Для вложенных объектов можно использовать несколько операторов:
//javascript
console.log(data?.user?.profile?.name);Если на одном из проверяемых уровней встретится null или undefined, дальнейшая часть этой непрерывной цепочки не будет выполняться, а результатом станет undefined.
Это удобно, когда отсутствие значения действительно является допустимой ситуацией.
Но optional chaining не исправляет причину появления undefined.
Представим страницу профиля. Пользователь должен быть загружен, но запрос неожиданно вернул неправильные данные.
Если написать:
//javascript
console.log(user?.name);ошибки не будет.
В консоли просто появится:
//text
undefinedА интерфейс может остаться без имени пользователя.
Поэтому если объект обязан существовать, optional chaining лучше не использовать только для того, чтобы спрятать ошибку. Сначала стоит выяснить, почему объект оказался undefined.
Иными словами, ?. - это способ безопасно обратиться к значению, которое действительно может отсутствовать, а не универсальное средство исправления ошибок.
Не путайте undefined и null
Есть похожая ошибка:
//text
Cannot read properties of nullНапример:
//javascript
const user = null;
console.log(user.name);Здесь проблема похожая, но значение другое.
undefined может появиться по разным причинам: свойство отсутствует, элемент массива не существует, функция ничего не вернула, переменной явно присвоили undefined или значение ещё не было установлено.
null - это уже явно установленное значение, которое означает отсутствие объекта.
В обоих случаях попытка:
//javascript
value.nameневозможна.
Поэтому при отладке важно посмотреть, что именно находится в переменной:
//javascript
console.log(value);Не стоит автоматически считать, что любой такой TypeError означает одно и то же.
Самая полезная привычка при этой ошибке
Если вижу:
//text
Cannot read properties of undefined (reading 'title')я не начинаю проверять, правильно ли написано title.
Я смотрю на выражение.
Например:
//javascript
article.author.nameЕсли ошибка говорит про name, проверяю:
//javascript
article.authorЕсли там:
//text
undefinedзначит проблема найдена.
Дальше уже можно выяснять причину:
API не вернул
author;объект сформирован неправильно;
элемент массива не существует;
функция ничего не вернула;
данные ещё не загрузились;
поле называется иначе;
условие выполнилось не так, как предполагалось.
То есть сама ошибка - это скорее указатель направления, чем полноценный диагноз.
Почему не стоит просто добавлять проверки повсюду
Можно написать:
//javascript
if (user && user.name) {
console.log(user.name);
}Или:
//javascript
console.log(user?.name);Иногда это именно то, что нужно.
Но если объект обязан существовать, бесконечно добавлять такие проверки - плохая стратегия.
Например:
//javascript
order?.customer?.profile?.nameможет убрать ошибку, но оставить непонятным, почему у заказа вдруг нет клиента.
В результате приложение формально не падает, но данные становятся пустыми.
Гораздо полезнее определить контракт данных.
Если функция должна возвращать пользователя, она должна возвращать пользователя.
Если API должен отдавать user, нужно проверить ответ API.
Если элемент массива может отсутствовать, это нужно учитывать при проектировании кода.
Иными словами, лучше исправить источник undefined, чем заставлять весь код постоянно его терпеть.
Что проверить в первую очередь
Когда появляется:
//text
Uncaught TypeError: Cannot read properties of undefinedя бы шёл в таком порядке.
Сначала нахожу строку с ошибкой.
Затем смотрю на выражение перед последним свойством:
//javascript
something.nameПроверяю:
//javascript
console.log(something);Если там undefined, выясняю, откуда оно пришло.
Если выражение длиннее:
//javascript
data.user.profile.nameразбираю его по частям:
//javascript
console.log(data);
console.log(data.user);
console.log(data.user?.profile);Так обычно гораздо быстрее становится понятно, на каком именно участке пропало значение.
Главное, что стоит запомнить
Ошибка:
//text
Cannot read properties of undefined (reading 'name')не означает:
«Свойство
nameнаписано неправильно».
Она означает:
«Я пытаюсь прочитать
name, но значение слева от точки оказалосьundefined».
И это небольшое изменение в понимании сильно упрощает отладку.
Вместо того чтобы искать ошибку в самом name, нужно подняться на один уровень выше и спросить:
//text
что находится слева от точки?Например:
//javascript
user.nameПроверяем:
//javascript
userДля:
//javascript
data.user.nameпроверяем:
//javascript
data.userДля:
//javascript
users[5].nameпроверяем:
//javascript
users[5]Как только вы начинаете читать TypeError именно таким образом, сообщение перестаёт выглядеть загадочным. JavaScript фактически уже сказал, что произошло. Нужно только правильно понять его формулировку.
Комментарии
Чтобы оставить комментарий, войдите в аккаунт.