Как обработать Uncaught (in promise) TypeError
Вы отлаживаете своё frontend-приложение, когда в консоли появляется сообщение: Uncaught (in promise) TypeError: Cannot read property 'type' of undefined. Это сообщение об ошибке содержит две различные проблемы, которые разработчики часто путают. Понимание обеих проблем необходимо для правильной обработки ошибок Promise в JavaScript.
В этой статье объясняется, что на самом деле означает эта ошибка, почему она появляется и как правильно обрабатывать необработанные отклонения Promise в браузерных окружениях.
Ключевые выводы
- Сообщение “Uncaught (in promise) TypeError” сигнализирует о двух отдельных проблемах: ошибке времени выполнения TypeError и отсутствующем обработчике отклонения Promise.
- Распространённые причины включают «плавающие» Promise (отсутствующий
await), забытые обработчики.catch()и обращение к свойствамundefinedилиnull. - Оборачивайте выражения
awaitвtry/catchили добавляйте.catch()в конец каждой цепочки Promise, чтобы предотвратить необработанные отклонения. - Используйте опциональную цепочку (
?.) и оператор нулевого слияния (??) для защиты от TypeError при обращении к несуществующим свойствам. - Используйте событие
unhandledrejectionдля мониторинга и телеметрии, а не как замену локальной обработке ошибок.
Что на самом деле означает ‘Uncaught (in promise) TypeError’
Это сообщение об ошибке указывает на две отдельные проблемы:
- TypeError: Произошла ошибка времени выполнения — обычно при попытке обращения к свойству
undefinedилиnull. - Uncaught (in promise): Promise, который вызвал эту ошибку, не имел присоединённого обработчика отклонения.
Браузер различает эти проблемы, потому что отклонения Promise ведут себя иначе, чем синхронные ошибки. Когда происходит синхронная ошибка TypeError, выполнение немедленно останавливается. Когда та же ошибка происходит внутри Promise, отклонение распространяется по цепочке Promise. Если ни .catch(), ни try/catch её не обрабатывают, браузер регистрирует её как необработанное отклонение Promise.
Для более глубокого понимания поведения Promise и распространения ошибок см. документацию MDN о Promise.
Распространённые первопричины во frontend-коде
Ошибки, возникающие внутри async-функций
Любое исключение внутри async-функции автоматически отклоняет возвращаемый Promise:
async function fetchUserData(userId) {
const response = await fetch(`/api/users/${userId}`)
const data = await response.json()
return data.profile.name // TypeError, если profile равен undefined
}
fetchUserData(123) // Плавающий Promise — обработчик не присоединён
Отсутствующие ключевые слова await
Забытый await создаёт плавающий Promise, который выполняется независимо:
async function processData() {
fetchUserData(123) // Отсутствует await — отклонение остаётся необработанным
console.log('Done')
}
Забытые обработчики .catch()
Цепочки Promise без завершающего блока .catch() оставляют отклонения необработанными:
fetch('/api/data')
.then(res => res.json())
.then(data => data.items[0].name) // Нет .catch() — TypeError распространяется
Позднее присоединение обработчика и время выполнения микрозадач
Браузеры определяют, обработано ли отклонение, после текущей контрольной точки микрозадач. Если к этому моменту у отклонения нет обработчика, браузер может отправить событие unhandledrejection и вывести предупреждение в консоль. Присоединение обработчика вскоре после этого всё равно может вызвать предупреждение в отладочных сборках:
const promise = Promise.reject(new Error('Failed'))
promise.catch(err => console.log('Handled'))
Эта особенность времени выполнения объясняет, почему некоторые правильно обработанные отклонения всё ещё могут отображаться как необработанные во время разработки.
Discover how at OpenReplay.com.
Правильные паттерны обработки ошибок с async/await
Локальный try/catch вокруг await
Наиболее надёжный паттерн — оборачивать выражения await в try/catch:
async function fetchPokemon(id) {
try {
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`)
const data = await response.json()
return {
name: data.name,
type: data.types[0]?.type.name,
type2: data.types[1]?.type.name ?? null
}
} catch (error) {
console.error('Fetch failed:', error.message)
return null
}
}
Возврат Promise с присоединёнными обработчиками
При вызове async-функций всегда обрабатывайте возвращаемый Promise:
// Вариант 1: await с try/catch
await fetchPokemon(25)
// Вариант 2: .catch() при вызове
fetchPokemon(25).catch(handleError)
Правильное использование цепочек .catch()
Размещайте .catch() в конце цепочек Promise, чтобы перехватить любое отклонение в цепочке:
Promise.all(urls.map(url => fetch(url).then(r => r.json())))
.then(results => processResults(results))
.catch(error => showErrorUI(error))
Использование события unhandledrejection для мониторинга
Браузеры предоставляют событие unhandledrejection для глобального мониторинга ошибок, а не в качестве основного механизма обработки ошибок:
window.addEventListener('unhandledrejection', event => {
// Логирование в сервис мониторинга ошибок
errorTracker.capture(event.reason)
// Опционально предотвращаем вывод ошибки в консоль по умолчанию
event.preventDefault()
})
Сопутствующее событие rejectionhandled срабатывает, когда ранее необработанное отклонение позже получает обработчик. Эти события полезны для телеметрии и для перехвата ошибок, которые проскользнули через вашу локальную обработку, но они не должны заменять правильные паттерны try/catch и .catch().
Заключение
Сообщение “Uncaught (in promise) TypeError” сигнализирует как об ошибке времени выполнения, так и об отсутствующей обработке ошибок. Решайте обе стороны проблемы: используйте опциональную цепочку (?.) и оператор нулевого слияния (??) для предотвращения TypeError при обращении к несуществующим свойствам, оборачивайте асинхронные операции в try/catch или присоединяйте обработчики .catch() к каждой цепочке Promise. Используйте событие unhandledrejection для мониторинга, а не для управления потоком выполнения. Эти паттерны сделают ваш frontend-код устойчивым, а консоль — чистой.
Часто задаваемые вопросы
Uncaught TypeError — это синхронная ошибка, которая немедленно останавливает выполнение. Uncaught (in promise) TypeError — это тот же тип ошибки времени выполнения, но она произошла внутри Promise, у которого не было обработчика отклонения. Браузер добавляет метку 'in promise', чтобы указать, что отклонение осталось необработанным в асинхронной цепочке Promise.
Да. Один .catch() в конце цепочки Promise перехватывает любое отклонение, которое происходит в любом предшествующем колбэке .then(). Отклонение распространяется вниз по цепочке, пока не найдёт обработчик .catch(). Если такого нет, браузер сообщает об этом как о необработанном отклонении Promise.
Блок try/catch внутри колбэка .then() может перехватывать синхронные ошибки, возникшие внутри этого колбэка. Однако он не может перехватить отклонения Promise, если эти Promise не ожидаются (await) внутри async-функции. Для обработки отклонений в цепочке .then() присоединяйте .catch() в конец цепочки. Используйте try/catch преимущественно для паттернов async/await.
Нет. Событие unhandledrejection — это страховочная сеть для перехвата отклонений Promise, которые проскользнули через вашу локальную обработку ошибок. Оно лучше всего подходит для логирования и мониторинга. Всегда обрабатывайте ошибки локально с помощью try/catch или .catch() в первую очередь, а глобальный обработчик событий используйте как резервный вариант для диагностики.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.