본문 바로가기
IT

Uncaught (in promise) — 처리 안 된 Promise 거부 원인·해결

by 샤나엘 2026. 6. 30.
반응형

Uncaught (in promise) — 처리 안 된 Promise 거부 원인·해결

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

Uncaught (in promise)

 

이 글의 구성

 

🔎에러 메시지의 의미
🧩자주 나오는 발생 원인 4가지
💻재현과 진단
🛠해결 방법 — try-catch·.catch
⚖️fetch가 404에 거부 안 하는 함정
💬자주 묻는 질문 5가지

🔎 에러 메시지의 의미

"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));

상황별 도구를 정리하면 이렇다. 핵심은 "거부가 흘러갈 끝에 반드시 받는 곳을 둔다"는 것이다.

상황별 처리 방법

 

01async/await → try-catch로 감싸 거부를 예외로 잡는다.
02.then 체인 → 끝에 .catch를 붙여 중간 거부를 모두 받는다.
03async 함수 호출부 → await + try-catch 또는 .catch를 빠뜨리지 않는다.
04일부 실패 허용 → Promise.all 대신 Promise.allSettled로 각 결과 처리.
05최후 안전망 → 전역 unhandledrejection 핸들러로 로깅(개별 처리 대체 X).

전역 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) 체크리스트

 

01콘솔 경고 뒤 거부 사유(Error)·스택으로 발생 위치 파악.
02async/await는 try-catch로, await를 try 블록 안에 둔다.
03.then 체인 끝에 .catch가 있는지 확인.
04fetch는 res.ok 확인 후 직접 throw(404·500 resolve 함정).
05일부 실패 허용은 Promise.allSettled로 전환.
06전역 unhandledrejection은 로깅 안전망으로만 보조.

본 글은 자바스크립트 비동기 Promise 거부 처리의 일반적 원인과 해결 방법을 정리한 자료다. 코드 수정은 영향 범위를 확인한 뒤 신중히 적용한다.

 

#JavaScript #Promise #Uncaughtinpromise #처리안된거부 #unhandledrejection #asyncawait #trycatch #fetch #resok #PromiseallSettled #비동기 #프론트엔드 #JS디버깅 #런타임에러 #웹개발

반응형

댓글