TypeError: Cannot read properties of undefined — как исправить

JavaScript Автор: Среда и версия: ECMAScript 2026
содержание

TypeError: Cannot read properties of undefined (reading 'name') означает, что JavaScript попытался прочитать свойство name у значения undefined. В длинной цепочке проблема находится не обязательно в name: значение слева от последней точки могло пропасть раньше.

Для null причина та же. V8 обычно пишет Cannot read properties of null, Firefox — null has no properties, Safari формулирует сообщение иначе. Во всех случаях это TypeError: код обращается к свойству значения, у которого свойств нет.

JavaScript / 01

reading name: проверяй объект перед .name

reading name: проверяй объект перед .name01 Сообщение …profile.name reading 'name' Чтение name произошло у undefined. 02 Раздели цепочку payload ✓ → user ✓ profile → undefined Найди первый пропавший объект, а не подставляй {} вслепую. 03 Проследи источник user.profile Проверь границу данных или порядок выполнения.01Сообщение…profile.namereading 'name'Чтение name произошло у undefined.02Раздели цепочкуpayload ✓ → user ✓profile → undefinedНайди первый пропавший объект, а не подставляй {} вслепую.03Проследи источникuser.profileПроверь границу данных или порядок выполнения.
В цепочке payload.user.profile.name сообщение называет свойство name, но проверять нужно значение слева: первым пропавшим звеном оказался user.profile.

Что именно означает Cannot read properties of undefined

У обычного объекта чтение отсутствующего свойства не вызывает исключение: результатом будет undefined. Ошибка появляется, когда код пытается продолжить цепочку и прочитать свойство уже у этого значения.

const user = {}

console.log(user.profile) // undefined
console.log(user.profile.name) // TypeError: Cannot read properties of undefined

В выражении user.profile.name движок сначала вычисляет user.profile. Получается undefined, после чего выполняется чтение .name. Именно в этот момент возникает TypeError.

С null происходит то же самое:

const user = { profile: null }

console.log(user.profile.name) // TypeError: Cannot read properties of null

По смыслу undefined обычно обозначает отсутствующее значение, а null — намеренное отсутствие объекта. Для этой ошибки различие не влияет на результат: читать свойства нельзя у обоих значений. При этом typeof null возвращает строку 'object' по исторической причине, поэтому проверка только через typeof value === 'object' недостаточна.

Как быстро найти значение, которое пропало

Начните со стека ошибки, а не с добавления ?. во все цепочки. Нужен первый кадр из кода приложения: он показывает файл, строку и выражение, которое выполнялось. Часть (reading 'name') называет свойство, которое движок пытался прочитать, но не источник undefined.

Затем разделите длинное выражение на промежуточные значения:

const user = payload?.user
const profile = user?.profile

console.log({ payload, user, profile })
console.log(profile.name)

Optional chaining здесь используется только как диагностический приём: он позволяет увидеть undefined, не остановившись на предыдущем звене. В отладчике можно поставить точку останова на строке ошибки и по очереди вычислить payload, payload.user и payload.user.profile.

После первого найденного undefined или null идите к месту, где это значение появилось:

  1. Какую функцию, запрос или поиск оно получило?
  2. Допускает ли контракт отсутствие результата?
  3. Если не допускает, где данные потерялись: при создании объекта, загрузке, преобразовании или передаче аргумента?

Такой порядок короче случайных правок. Он отделяет место падения от источника неверного значения.

Поиск не нашёл элемент

Array.prototype.find() возвращает первый подходящий элемент, а если совпадения нет — undefined. Следующее чтение свойства и даёт ошибку:

const users = [{ id: 1, name: 'Аня' }]
const user = users.find((item) => item.id === 7)

console.log(user.name) // TypeError: Cannot read properties of undefined

Если отсутствие пользователя — нормальный исход, обработайте его сразу после поиска:

function getUserName(users, requestedId) {
  const user = users.find((item) => item.id === requestedId)

  if (user === undefined) {
    return 'Пользователь не найден'
  }

  return user.name
}

Если пользователь обязан существовать, запасная строка только скроет нарушение контракта. В таком случае выбросьте понятную ошибку с идентификатором или исправьте данные, из-за которых поиск ничего не находит.

Тот же принцип относится к чтению отсутствующего ключа объекта и индекса за пределами массива: сначала проверьте контракт операции, которая вернула значение, затем решайте, допустимо ли отсутствие.

Объект читают до инициализации

Переменная может быть объявлена, но ещё не получить объект. Частая причина — неверный порядок вызовов:

let settings

renderTheme(settings.theme)
settings = loadSettings()

Переставить строки недостаточно, если loadSettings() асинхронна. Но для синхронной инициализации исправление прямое: сначала создать значение, затем передать его потребителю.

const settings = loadSettings()
renderTheme(settings.theme)

Ещё один источник — объект разной формы в разных ветках:

let session

if (hasToken) {
  session = { user: currentUser }
}

showName(session.user.name)

Здесь нужно определить оба состояния явно. Если без токена показывается гостевой экран, верните его до чтения session.user. Если токен обязателен, остановите выполнение с понятной ошибкой. Присваивание session = {} перед if лишь передвинет падение с session.user на session.user.name.

Асинхронные данные ещё не готовы

Код после запуска промиса продолжает выполняться. Поэтому переменная, которую присвоят в then(), остаётся undefined до завершения операции:

let account

loadAccount().then((value) => {
  account = value
})

showName(account.profile.name)

Свяжите чтение с результатом промиса. В async-функции это обычно один await:

async function showAccount() {
  const account = await loadAccount()
  showName(account.profile.name)
}

await решает только вопрос порядка. Сервер всё равно может вернуть объект без profile, поэтому ответ внешнего API нужно проверить перед передачей в основную логику. Не лечите гонку произвольным setTimeout: задержка не создаёт гарантии, что операция уже завершилась.

DOM-поиск вернул null

document.getElementById() возвращает null, если элемента с таким id нет в документе. Причиной может быть опечатка в идентификаторе, запуск скрипта до создания разметки или страница, на которой этот элемент не предусмотрен.

const heading = document.getElementById('profile-name')
const currentName = heading.textContent

Если элемент обязателен для этой страницы, проверьте результат рядом с DOM-поиском:

const heading = document.getElementById('profile-name')

if (heading === null) {
  throw new Error('Не найден элемент #profile-name')
}

const currentName = heading.textContent

Если элемент необязателен, ветка if (heading !== null) точнее описывает намерение. Но сначала проверьте, почему поиск ничего не нашёл: регистр id учитывается, а сам элемент должен уже находиться в дереве документа.

Исправляйте данные на границе

Лучшее место для проверки — там, где ненадёжные данные входят в программу: после ответа API, чтения хранилища, поиска элемента или вызова функции, которая допускает пустой результат. Тогда внутренний код получает объект известной формы и не повторяет одинаковые проверки.

Для небольшого фрагмента достаточно обычных условий:

const payload = await response.json()

if (payload === null || typeof payload !== 'object') {
  throw new TypeError('Ответ API должен быть объектом')
}

const user = payload.user
if (user === null || typeof user !== 'object') {
  throw new TypeError('В ответе API отсутствует user')
}

const profile = user.profile
if (profile === null || typeof profile !== 'object') {
  throw new TypeError('В ответе API отсутствует user.profile')
}

if (typeof profile.name !== 'string') {
  throw new TypeError('user.profile.name должен быть строкой')
}

showName(profile.name)

В проекте с готовой схемой валидации используйте её, а не пишите второй валидатор. Важно не название инструмента, а момент проверки: до того, как неполный объект разойдётся по приложению.

Отсутствие иногда входит в контракт. Например, незаполненный город профиля можно показать как «не указан». Тогда явная запасная ветка корректна:

const city = profile.address?.city ?? 'Не указан'

Для обязательного profile.name такая подстановка уже меняет смысл: вместо ошибки данных интерфейс молча покажет гостя или пустую строку.

Почему optional chaining и значения по умолчанию могут скрыть дефект

Оператор ?. прекращает непрерывную цепочку, если слева null или undefined, и возвращает undefined вместо исключения. Оператор ?? подставляет правую часть только для этих двух значений.

const name = response?.user?.profile?.name ?? 'Гость'

Этот код безопасен, если по правилам продукта user, profile и name действительно могут отсутствовать, а «Гость» — предусмотренное отображение. Если хотя бы одно поле обязательно, выражение скрывает повреждённый ответ: стек исчезает, интерфейс выглядит рабочим, а причину сложнее найти.

Подстановка пустого объекта создаёт похожий эффект:

const profile = response.user?.profile ?? {}
const normalizedName = profile.name.toUpperCase()

Ошибка не исправлена, а перенесена на следующую строку: теперь profile.name равно undefined. Запасное значение должно быть полноценным допустимым состоянием, а не способом убрать красное сообщение из консоли.

Используйте ??, а не ||, когда запасное значение нужно только для null и undefined. Оператор || также заменит корректные 0, false и пустую строку.

Похожие TypeError требуют другой диагностики

Эта статья разбирает чтение: value.property. У похожих сообщений другой момент сбоя:

  • Cannot set properties of undefined возникает при записи value.property = ...; сначала проверяют объект слева от присваивания.
  • Ошибка деструктуризации undefined или null появляется, когда код извлекает свойства конструкцией вроде const { name } = value; там важны источник значения и значение по умолчанию для всей правой части.
  • is not a function означает, что выражение дошло до вызова, но найденное значение нельзя вызвать. Это не обязательно undefined: в свойстве может лежать строка, объект или число.

Разделение по намерению экономит время: чтение, запись, деструктуризация и вызов проходят разные операции языка, даже если рядом стоит одно и то же имя свойства.

Чек-лист диагностики

  1. Найдите в стеке первую строку из кода приложения. Если стек состоит из тысяч повторов одной и той же строки, ошибка другая — это Maximum call stack size exceeded, и чинить надо рекурсию.
  2. Выпишите свойство из части (reading '...').
  3. Разбейте цепочку и найдите первое значение undefined или null.
  4. Проследите, откуда оно пришло: поиск, инициализация, промис, API или DOM.
  5. Решите по контракту, допустимо ли отсутствие.
  6. Обработайте ожидаемое отсутствие сразу после операции, которая его возвращает.
  7. Для обязательных данных выбросьте понятную ошибку или исправьте источник.
  8. Добавляйте ?., ?? и пустые значения только там, где они описывают реальное состояние продукта.

Главный ориентир — значение слева от свойства. Найдите первое пропавшее звено, затем исправьте место, где оно создаётся или входит в программу. Так ошибка исчезнет вместе с причиной, а не только с конкретной строкой в стеке.

Источники