Uncaught (in promise) — 처리 안 된 Promise 거부 원인·해결
비동기 코드를 짜다 보면 콘솔에 "Uncaught (in promise)"가 뜰 때가 있다. 다른 에러와 달리 코드가 멈추진 않는데, 빨간 경고가 거슬리고 정작 에러 처리는 안 되고 있다는 신호다. 뜻은 단순하다. 어떤 Promise가 거부(reject)됐는데, 그 거부를 잡아 줄 .catch나 try-catch가 어디에도 없다는 것이다. fetch 요청이 실패했거나, async 함수 안에서 에러가 났는데 아무도 받지 않은 경우다. 특히 fetch가 404·500 같은 HTTP 에러에는 거부조차 하지 않는다는 함정이 이 경고를 부르는 단골 원인이다. 이 글은 이 경고의 의미, 원인, 그리고 try-catch와 .catch를 비롯한 해결을 정리한다.

이 글의 구성
🔎 에러 메시지의 의미
"Uncaught (in promise)"는 Promise가 거부됐는데 그 거부를 처리하는 핸들러가 하나도 없을 때 뜨는 경고다. Promise는 성공(resolve)과 실패(reject) 두 갈래로 끝나는데, 실패했을 때 .catch나 try-catch로 받아 주지 않으면 그 거부가 "잡히지 않은 채" 떠다닌다. 그걸 자바스크립트 런타임이 감지해 콘솔에 알리는 것이다.
메시지 뒤에는 보통 거부 사유가 따라온다. Uncaught (in promise) Error: Network request failed 같은 식으로, 거부에 담긴 Error 객체나 값이 함께 표시된다. 이 사유가 "무엇이 실패했는지"를 알려주는 단서다. 다른 런타임 에러와 달리 페이지 전체를 멈추진 않지만(거부된 async 함수에서는 그 뒤 코드가 중단된다), 그건 "괜찮다"가 아니라 "에러가 조용히 무시되고 있다"는 뜻이라 더 위험할 수 있다.
핵심 — 거부됐는데 아무도 안 받았다
이 경고는 "에러가 났다"가 아니라 "에러는 났는데 처리가 안 됐다"는 신호다. 그래서 해결은 거부를 일으킨 원인을 고치는 것과, 그 거부를 받아 줄 .catch·try-catch를 다는 것 두 가지다. 경고를 없애려고 무작정 잡기만 하면, 정작 실패를 사용자에게 알리거나 복구하는 처리가 빠질 수 있다.
🧩 자주 나오는 발생 원인 4가지
원인 1 — await에 try-catch가 없음
가장 흔하다. async 함수 안에서 await한 Promise가 거부되면 그 자리에서 예외가 던져지는데, 감싸는 try-catch가 없으면 그 예외가 처리 안 된 거부로 떠오른다.
원인 2 — .then만 쓰고 .catch가 없음
Promise 체인을 .then으로만 잇고 끝에 .catch를 안 붙인 경우다. 중간 어디서든 거부가 나면 받을 곳이 없어 그대로 처리 안 된 거부가 된다.
원인 3 — async 함수 호출에 처리를 안 붙임
async 함수를 호출만 하고 await도 .catch도 안 붙인 경우다. 그 함수 안에서 거부가 나면, 호출부에서 받지 않으니 처리 안 된 거부로 이어진다. 이벤트 핸들러에서 async 함수를 부를 때 자주 생긴다.
원인 4 — Promise.all 중 하나가 거부
Promise.all은 하나라도 거부되면 전체가 즉시 거부된다. 그 거부를 .catch로 안 받으면 경고가 뜬다. 일부 실패를 견뎌야 한다면 Promise.allSettled가 더 알맞다.
💻 재현과 진단
전형적인 재현이다. async 함수 안에서 거부되는 Promise를 await하면서 try-catch를 안 두면 경고가 뜬다.
// try-catch 없이 거부되는 await
async function load() {
const res = await fetch("/api/data");
return await res.json();
}
load(); // 호출부에도 catch 없음
// ✗ Uncaught (in promise) TypeError: Failed to fetch
진단의 출발점은 메시지 뒤의 거부 사유다. 콘솔에서 경고를 펼치면 어떤 Error가, 어느 줄에서 났는지 스택이 나온다. 어디서 거부가 시작됐는지 모를 때는 전역 핸들러로 모든 처리 안 된 거부를 잡아 로깅해 위치를 좁힐 수 있다.
// 처리 안 된 거부를 전역에서 감지 (진단·로깅용)
window.addEventListener("unhandledrejection", (e) => {
console.error("처리 안 된 거부:", e.reason);
});
🛠 해결 방법 — try-catch·.catch
해결은 거부를 받아 줄 곳을 다는 것이다. async/await에는 try-catch가, Promise 체인에는 .catch가 짝이다.
// ✓ async/await — try-catch 로 감싸기
async function load() {
try {
const res = await fetch("/api/data");
return await res.json();
} catch (e) {
console.error("불러오기 실패:", e);
}
}
// ✓ Promise 체인 — 끝에 .catch
fetch("/api/data")
.then(res => res.json())
.catch(err => console.error(err));
상황별 도구를 정리하면 이렇다. 핵심은 "거부가 흘러갈 끝에 반드시 받는 곳을 둔다"는 것이다.
상황별 처리 방법
전역 unhandledrejection 핸들러는 어디까지나 최후의 안전망이다. 모든 거부를 한곳에서 로깅해 놓치는 걸 막아 주지만, 실패한 요청을 재시도하거나 사용자에게 알리는 구체적 처리를 대신하진 못한다. 그래서 거부가 날 만한 자리마다 제대로 .catch·try-catch를 두는 게 먼저다.
⚖️ fetch가 404에 거부 안 하는 함정
이 경고에서 가장 많이 헷갈리는 지점이 fetch의 동작이다. 흔한 오해는 "fetch가 404·500이면 거부될 것"이라는 생각인데, 사실은 그렇지 않다.
| 상황 | fetch 결과 | 처리 |
|---|---|---|
| 네트워크 실패·요청 자체 불가 | 거부(reject) | try-catch·.catch로 잡힘 |
| 404·500 등 HTTP 에러 | 정상 resolve(ok=false) | res.ok 직접 확인 필요 |
fetch는 네트워크 자체가 실패했을 때만 거부한다. 서버가 404나 500으로 응답한 경우는 "응답을 받긴 받았다"고 보아 정상 resolve하며, 다만 res.ok가 false가 된다. 그래서 res.ok를 확인하지 않으면 에러 응답을 정상으로 착각하고, 그 본문을 파싱하다 엉뚱한 곳에서 거부가 나 "Uncaught (in promise)"로 이어진다. 해법은 응답을 받은 뒤 res.ok를 검사해 직접 에러를 던지는 것이다.
// ✓ res.ok 확인 후 직접 에러 던지기
async function load() {
try {
const res = await fetch("/api/data");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (e) {
console.error("요청 실패:", e);
}
}
💬 자주 묻는 질문 5가지
Q1코드는 도는데 경고만 떠요. 무시해도 되나요?
권하지 않습니다. 경고가 떴다는 건 어떤 비동기 작업이 실패했는데 그 실패가 처리되지 않고 무시되고 있다는 뜻입니다. 지금은 멀쩡해 보여도 사용자에게 실패를 못 알리거나 복구 로직이 빠진 상태죠. 거부가 나는 자리에 .catch·try-catch를 다는 게 맞습니다.
Q2fetch로 404를 받는데 catch에 안 걸려요.
fetch는 네트워크 실패에만 거부하고, 404·500 같은 HTTP 에러는 정상 resolve합니다. 그래서 catch에 안 걸리죠. 응답을 받은 뒤 res.ok가 false면 직접 throw해서 에러로 만들어야 catch로 흐릅니다. fetch의 대표적 함정입니다.
Q3try-catch를 했는데도 경고가 떠요.
await 없이 호출한 Promise는 그 try-catch 안에 들어오지 않습니다. try 블록 안에서 await를 붙여야 거부가 예외로 잡힙니다. 또 .then 안에서 새로 만든 Promise나, catch보다 뒤에 이어진 체인의 거부는 별도로 처리해야 합니다.
Q4Promise.all에서 자꾸 나요.
Promise.all은 하나라도 거부되면 전체가 즉시 거부됩니다. 그 거부를 .catch나 try-catch로 받지 않으면 경고가 뜨죠. 일부가 실패해도 나머지 결과를 쓰고 싶다면 Promise.allSettled를 쓰면 각 작업의 성공·실패를 따로 받을 수 있습니다. 한 가지 더, Promise.all에 .catch를 달아도 이미 시작된 다른 Promise들은 취소되지 않아, 그중 하나가 뒤늦게 거부되면 또 경고가 날 수 있습니다.
Q5전역 unhandledrejection만 달면 끝인가요?
최후의 안전망일 뿐 근본 해결은 아닙니다. 모든 처리 안 된 거부를 한곳에서 로깅해 놓침을 막아 주지만, 실패한 요청 재시도나 사용자 알림 같은 구체적 처리는 못 합니다. 거부가 날 자리마다 제대로 .catch·try-catch를 두고, 전역 핸들러는 보조로 쓰세요.
📌 결론
"Uncaught (in promise)"는 거부된 Promise를 받아 줄 핸들러가 없을 때 뜨는 경고다. 페이지 전체를 멈추진 않지만, 그건 "괜찮다"가 아니라 "에러가 조용히 무시되고 있다"는 신호다. 그래서 거부를 받을 곳을 제대로 다는 게 핵심이다.
실무 원칙은 다음과 같다.
🎣 거부를 반드시 받는다. async/await엔 try-catch, .then 체인엔 .catch를 끝에 붙여 거부가 흘러갈 종착지를 만든다.
🛡 fetch 함정을 기억한다. fetch는 네트워크 실패에만 거부하고 404·500은 resolve한다. res.ok를 확인해 직접 에러를 던져야 catch로 흐른다.
🌐 전역은 보조로만 쓴다. unhandledrejection 핸들러는 놓침 방지용 로깅이고, 실패 자리마다의 개별 처리를 대신하지 않는다. 일부 실패 허용엔 Promise.allSettled를 쓴다.
Uncaught (in promise) 체크리스트
본 글은 자바스크립트 비동기 Promise 거부 처리의 일반적 원인과 해결 방법을 정리한 자료다. 코드 수정은 영향 범위를 확인한 뒤 신중히 적용한다.
#JavaScript #Promise #Uncaughtinpromise #처리안된거부 #unhandledrejection #asyncawait #trycatch #fetch #resok #PromiseallSettled #비동기 #프론트엔드 #JS디버깅 #런타임에러 #웹개발
'IT' 카테고리의 다른 글
| Cannot read properties of null (reading 'x') — 원인·해결·예방 (0) | 2026.07.01 |
|---|---|
| Maximum call stack size exceeded — 무한 재귀 원인·해결 (0) | 2026.06.30 |
| x is not a function — 원인·해결·예방 (0) | 2026.06.30 |
| SyntaxError: Unexpected token — 원인·해결·예방 (0) | 2026.06.30 |
| ReferenceError: x is not defined — 원인·해결·예방 (1) | 2026.06.29 |
댓글