TypeError: Cannot read properties of undefined — что делать
Эта ошибка — самый частый гость на ревью кода и в проде. 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 минут после деплоя.
Шпаргалка для дебага
- Прочитай стек, найди строку.
- Определи, какой объект undefined.
- Логни его прямо перед ошибкой и его источник.
- Найди, где должен был присваиваться. Был ли await? Был ли valid-ответ от API?
- Лечи в источнике, не маскируй
?.над всеми обращениями подряд. - На границе системы добавь zod-валидацию, чтобы такая ошибка ловилась раньше и осмысленнее.
TypeError — это не «таинственная ошибка JavaScript». Это просто способ движка сказать «ты обращаешься к свойству у того, кого нет». Чем строже у тебя типизация и валидация на границах системы, тем реже будешь ловить эту ошибку. Но даже в чистом JS три-четыре приёма выше закрывают 90% случаев.