lenec ru

← все посты

TypeError: Cannot read properties of undefined — что делать

10K

Эта ошибка — самый частый гость на ревью кода и в проде. TypeError: Cannot read properties of undefined (reading 'X') или старый вариант «of null». Знакомо. Расскажу, как я её ловлю и какие у неё бывают типичные причины. Без воды, по сути.

Что говорит сама ошибка

Сообщение в свежих движках максимально полезное:

TypeError: Cannot read properties of undefined (reading 'name')
    at processOrder (/app/dist/orders.js:42:18)
    ...

Из этого видно:

  • Ошибка в строке 42 файла orders.js.
  • Пытались прочитать поле name.
  • Объект, у которого читали, оказался undefined.

То есть в выражении что_то.name «что_то» = undefined. Дальше задача — понять, почему «что_то» оказалось пустым.

Самые частые причины

1. Деструктуризация без проверки

const { user } = data;
const { name } = user; // если user undefined — TypeError

Лечится либо проверкой, либо optional chaining:

const name = data?.user?.name;

Если data или data.user undefined — получишь name = undefined, а не падение.

2. Async и забытый await

// плохо
function loadOrder(id) {
  return fetchOrder(id);  // возвращаем Promise, а не результат
}
const order = loadOrder(123);
console.log(order.total); // TypeError: undefined у Promise нет свойства total

Точнее, у Promise есть свойства, но total — нет. Лечится либо await, либо .then():

const order = await loadOrder(123);
console.log(order.total);

На моём опыте, после optional chaining это причина номер два. Особенно у тех, кто только переходит с Promise-стиля на async/await.

3. Ответ API не такой, как ожидал

const data = await fetch('/api/user').then(r => r.json());
console.log(data.profile.name); // TypeError, если API вернул { error: '...' }

Если API в случае ошибки возвращает другой формат — твой код упадёт. Лечится либо проверкой формата, либо валидацией через zod:

import { z } from 'zod';

const userSchema = z.object({
  profile: z.object({
    name: z.string(),
  }),
});

const raw = await fetch('/api/user').then(r => r.json());
const data = userSchema.parse(raw);
console.log(data.profile.name);

На несоответствии — zod кинет осмысленное исключение. На входе в систему ты теперь знаешь, что за форма данных.

3. Опечатка в имени поля

const user = { firstName: 'Anna' };
console.log(user.first_name.length); // TypeError

Поля first_name в объекте нет. user.first_name = undefined, дальше .length падает. Идеально ловится TypeScript-ом — если у тебя обычный JS, это сложнее. Лечится перепиской на TS или хотя бы JSDoc-аннотациями + ESLint-плагином.

5. Неправильный this

class User {
  constructor(name) { this.name = name; }
  greet() { return `Hello, ${this.name}`; }
}
const u = new User('Anna');
const greet = u.greet;
console.log(greet()); // TypeError: cannot read properties of undefined (reading 'name')

В strict-mode this = undefined, и this.name падает. Лечится const greet = u.greet.bind(u) или arrow-методами.

6. Глобальная переменная не инициализирована

let cache;
// ... код, который должен инициализировать cache
console.log(cache.get('key')); // TypeError, если инициализации не произошло

Часто бывает в условиях с if-else, где cache назначается только в одной ветке. Лечится более строгой типизацией или явным fallback.

Шаги диагностики

Шаг 1: смотри на стек

Не угадывай. Стек скажет, в каком файле и в какой строке. Если у тебя production-bundle с минификацией — обязательно включи source maps (--enable-source-maps в Node), иначе в строке будет каша.

Шаг 2: console.log того, что должно быть объектом

Перед строкой с ошибкой добавь:

console.log('user before:', user, typeof user);
console.log(user.name);

Сразу будет видно: user before: undefined undefined. Дальше задача — найти, где user должен был быть присвоен, и почему не присвоен.

Шаг 3: проверь источник данных

Если объект приходит из API или БД — лог исходного ответа. Может быть, БД вернула пустой массив, а ты обращаешься к [0].

const rows = await db.select().from(users).where(eq(users.id, id));
console.log('rows:', rows);
const user = rows[0];
console.log(user.name); // TypeError, если rows пустой

Лечение:

const user = rows[0];
if (!user) throw new Error(`User ${id} not found`);
console.log(user.name);

Шаг 4: дебагер вместо log

В Node делается через node --inspect + Chrome DevTools или VSCode. Ставишь breakpoint на строке с ошибкой, смотришь scope. Этот способ существенно быстрее, чем расставлять log-и.

Профилактика

TypeScript

Самая большая разница. Strict-режим TypeScript ловит больше половины таких ошибок до запуска. Если поле может быть undefined, тип так и говорит: string | undefined, и доступ к свойству ругается ещё на этапе компиляции.

function processUser(user: User | undefined) {
  console.log(user.name); // ts error: 'user' is possibly 'undefined'
  console.log(user?.name); // ok, name: string | undefined
}

Optional chaining и nullish coalescing

Используй ?. и ?? везде, где значение может отсутствовать.

const name = user?.profile?.name ?? 'Anonymous';

На длинных цепочках это спасает. ?? в отличие от || обрабатывает только null и undefined, а не falsy-значения, что обычно правильнее.

Валидация на границе системы

Любые внешние данные (API, БД, форма от пользователя) проверяй через zod, valibot или yup. Внутри системы потом работай с типизированными значениями, у которых ты знаешь форму.

Не возвращай undefined из функций

// плохо: иногда возвращает результат, иногда undefined
function findUser(id) {
  if (id) return users.find(u => u.id === id);
}
// лучше: явно указываем, что возможно null
function findUser(id) {
  if (!id) return null;
  return users.find(u => u.id === id) ?? null;
}

Возвращай null или throw, не undefined. Это явный сигнал «значения нет», а не «забыл вернуть».

Когда ошибка повторяется в проде

Установи Sentry или похожий error-tracker. Он покажет:

  • Стек, версию приложения, окружение.
  • Сколько пользователей затронуто.
  • Какие действия делали перед падением (breadcrumbs).

Без error-tracker-а в проде ты ловишь ошибки только через жалобы пользователей. С ним — видишь регрессию через 5 минут после деплоя.

Шпаргалка для дебага

  1. Прочитай стек, найди строку.
  2. Определи, какой объект undefined.
  3. Логни его прямо перед ошибкой и его источник.
  4. Найди, где должен был присваиваться. Был ли await? Был ли valid-ответ от API?
  5. Лечи в источнике, не маскируй ?. над всеми обращениями подряд.
  6. На границе системы добавь zod-валидацию, чтобы такая ошибка ловилась раньше и осмысленнее.

TypeError — это не «таинственная ошибка JavaScript». Это просто способ движка сказать «ты обращаешься к свойству у того, кого нет». Чем строже у тебя типизация и валидация на границах системы, тем реже будешь ловить эту ошибку. Но даже в чистом JS три-четыре приёма выше закрывают 90% случаев.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

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