Uncaught TypeError: не удаётся прочитать свойство undefined - что означает эта ошибка JavaScript

mr. Cooper 1 неделю назад Веб-разработка
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 фактически уже сказал, что произошло. Нужно только правильно понять его формулировку.

Комментарии

Пока нет комментариев. Будьте первым, кто напишет.

Чтобы оставить комментарий, войдите в аккаунт.

Похожие статьи