Menu

자바스크립트 try/catch: 에러 처리 완벽 가이드

자바스크립트 try/catch/finally 문법부터 error 객체, 에러 다시 던지기, async/await과 함께 쓰는 법까지 실전 예제로 정리했습니다.

try/catch는 안전벨트가 아니라 안전망이다

자바스크립트에서 어떤 줄이 예외를 던지면, 실행은 그 자리에서 멈추고 에러는 콜 스택을 타고 위로 전파된다. 아무도 이 에러를 잡아주지 않으면 Node에서는 프로그램이 그대로 죽고, 브라우저에서는 콘솔이 빨간 글씨로 도배된다. 이럴 때 쓰는 게 바로 자바스크립트 try catch다. "여기서 실패할 수도 있으니까, 그러면 이렇게 처리할게"라고 알려주는 장치라고 보면 된다.

기본 형태는 이렇다:

index.js
Output
Click Run to see the output here.

JSON.parseSyntaxError를 던지면, 실행 흐름은 곧바로 catch 블록으로 넘어가고 에러 객체는 err에 담깁니다. 그래도 세 번째 console.log는 정상적으로 실행됩니다. 에러가 바깥으로 새어 나가지 않고 안전하게 막힌 거죠.

try 블록이 아무 문제 없이 끝나면 catch 블록은 아예 건너뜁니다. catch는 어디까지나 실패 상황을 위한 장치니까요.

에러 객체 살펴보기

try 블록에서 던져진 값은 catch (...)에 적어둔 이름으로 바인딩됩니다. 대부분은 Error 인스턴스이고, 다음 세 가지 속성이 실무에서 유용하게 쓰입니다:

index.js
Output
Click Run to see the output here.

name은 어떤 하위 클래스인지 알려줍니다 (TypeError, RangeError, SyntaxError 등 — 자세한 내용은 다음 문서에서 다룹니다). message는 사람이 읽을 수 있는 설명이고, stack은 전체 스택 트레이스입니다. 디버깅할 때 정말 요긴하죠.

한 가지 주의할 점이 있습니다. 자바스크립트에서는 Error 객체가 아닌 것도 얼마든지 throw할 수 있어요. 오래된 코드를 보면 throw "뭔가 망가짐" 같은 식으로 문자열을 그대로 던지는 경우가 종종 있습니다. 하지만 직접 throw 할 때는 호출한 쪽에서 스택 트레이스를 받을 수 있도록 항상 Error 객체를 던지세요:

index.js
Output
Click Run to see the output here.

finally는 무조건 실행된다

finally는 선택적으로 붙일 수 있는 세 번째 블록인데, 에러가 났든 안 났든, catch에서 처리했든 안 했든 무조건 실행된다. 주로 뒷정리 용도로 쓴다 — 파일 닫기, 락 해제, 로딩 스피너 숨기기 같은 작업이 대표적이다:

index.js
Output
Click Run to see the output here.

로드가 성공했든 실패했든 스피너는 반드시 숨겨집니다. 만약 finally가 없다면 이 줄을 try와 catch 양쪽에 모두 써야 하는데, 그러다 보면 한쪽에서 빼먹기 십상이죠.

finallytrycatch 블록 안에 return이 있어도 실행됩니다. 함수는 finally끝난 뒤 값을 반환하죠. 가끔은 당황스러울 수 있지만, 대부분은 바로 이런 동작을 원해서 쓰는 겁니다.

catch가 꼭 필요한 건 아닙니다

catch는 선택 사항입니다. 에러를 직접 처리할 생각은 없지만 정리(cleanup) 작업만큼은 반드시 보장하고 싶을 때, try/finally만 쓰는 것도 충분히 유효합니다. 에러는 그대로 위로 전파되도록 두는 거죠:

index.js
Output
Click Run to see the output here.

안쪽의 try/finallyfn()이 예외를 던져도 락을 확실히 풀어주지만, 에러 자체를 삼켜버리지는 않습니다. 호출자 쪽에는 에러가 그대로 전달되죠. 에러를 조용히 삼키는 것("어딘가에서 실패했는데 아무도 모르는" 상황)은 디버깅할 때 가장 골치 아픈 상황 중 하나입니다.

에러 다시 던지기: 일부만 처리하고 나머지는 위로 올리기

catch 블록에서 모든 에러를 다 처리할 필요는 없습니다. 에러를 들여다보고, 처리할 만한 건 직접 처리한 뒤 나머지는 다시 던져버리면 됩니다.

index.js
Output
Click Run to see the output here.

instanceof로 타입을 검사하는 건 일종의 패턴이라고 볼 수 있어요. 내가 복구할 수 있는 에러만 잡고, 나머지는 호출 스택 위로 그대로 올려보내는 거죠. 반대로 빈 catch 블록으로 모든 에러를 삼켜 버리는 건 전형적인 코드 스멜입니다. 예상 못한 문제가 터졌을 때 아무 단서도 남지 않거든요.

async/await에서 try catch 사용법

async 함수 안에서 await한 프로미스가 reject되면 그게 곧 throw된 에러로 바뀝니다. 그래서 동기 코드의 에러와 똑같이 try/catch로 잡을 수 있어요:

index.js
Output
Click Run to see the output here.

한 가지 주의할 점이 있는데, 반드시 try 블록 안에서 프로미스를 await해야 한다는 점입니다. await 없이 프로미스를 그냥 반환해 버리면, 함수가 이미 종료된 뒤에 rejection이 발생하기 때문에 catch가 그 에러를 잡지 못합니다:

async function bad() {
  try {
    return fetch("/broken");  // await 없음 — 호출자가 rejection 을 받게 됨
  } catch (err) {
    // 실행되지 않음
  }
}

핵심 원칙: async 함수 안에서는 try/catch로 감싸고 싶은 대상에 꼭 await를 붙이세요.

중첩 try catch

안쪽 코드와 바깥쪽 코드에서 서로 다른 이유로 에러가 발생하고, 각각을 다르게 처리하고 싶다면 try/catch를 중첩해서 쓸 수 있습니다.

index.js
Output
Click Run to see the output here.

안쪽 catch는 "데이터 구조가 잘못된" 경우를 안전한 기본값을 반환해서 처리하고, 바깥쪽 catch는 "애초에 JSON이 아니었던" 경우를 감싸서 다시 던지는 역할을 맡습니다. 이렇게 각 계층마다 복구 전략이 뚜렷하게 다르다면 중첩 try catch도 괜찮습니다. 반대로 두 블록이 결국 같은 일을 한다면, 하나로 합치는 게 낫습니다.

try catch를 쓰지 말아야 할 때

try/catch예상 가능하고 복구할 수 있는 실패를 다루기 위한 도구입니다. 버그를 덮어두는 용도가 아니에요.

  • "혹시 모르니까" 함수 전체를 감싸지 마세요. 에러에 대한 실질적인 대응 계획이 없다면 그냥 위로 전파시키세요. 스택 트레이스가 찍힌 미처리 에러가 조용히 삼켜진 에러보다 훨씬 쓸모 있습니다.
  • 흐름 제어용으로 쓰지 마세요. try 블록은 실제로 오버헤드가 있고, 단순한 if 체크에 비해 코드도 지저분해집니다. try { user.name } catch {}보다 if (user)가 낫습니다.
  • catch해서 로그만 찍고 무시하는 패턴도 피하세요. 최소한 다시 던지거나, 호출자가 감지할 수 있는 센티널 값을 반환해야 합니다.

판단 기준은 이겁니다. "이 코드를 쓰는 쪽에서 이 에러가 났을 때 뭘 해야 하는가?" 답이 바로 떠오르지 않는다면, 아직 그 에러를 catch할 단계가 아닙니다.

빠른 참고: try catch 사용법

  • try { ... } catch (err) { ... } — 던져진 에러를 가로챕니다.
  • finally { ... } — 항상 실행됩니다. 정리 작업에 사용하세요.
  • throw new Error("...") — 스택 트레이스가 동작하도록 항상 Error의 하위 클래스를 던지세요.
  • catch 안에서 throw err; — 처리할 수 없는 에러는 다시 던지세요.
  • try 안의 awaittry/catch가 비동기 reject를 잡으려면 반드시 필요합니다.

다음: 에러 타입

TypeError, RangeError, SyntaxError — 자바스크립트에는 내장 에러 클래스 계열이 있습니다. 각 에러가 어떤 의미인지 알아두면 에러를 잡고 보고하는 일이 훨씬 정밀해집니다. 다음 문서에서 다뤄볼 주제입니다.

자주 묻는 질문

자바스크립트 try/catch는 어떻게 동작하나요?

에러가 날 수 있는 코드를 try { ... } 안에 넣어두면, 실행 중 뭔가 throw될 때 바로 catch (err) { ... } 블록으로 넘어가면서 던져진 값이 err에 담깁니다. 아무 일도 없으면 catch는 건너뛰고요. 옵션인 finally { ... }는 에러 여부와 상관없이 무조건 실행되기 때문에 자원 정리 용도로 딱입니다.

try/catch는 언제 써야 하나요?

실제로 런타임에 실패할 가능성이 있는 동작에만 씌우세요. 예를 들어 신뢰할 수 없는 입력에 JSON.parse를 돌릴 때, fetch 응답을 다룰 때, 파일이나 네트워크 I/O를 할 때가 대표적입니다. 모든 줄을 감쌀 필요는 없어요. 복구할 방법이 없다면 그냥 에러가 위로 전파되도록 두는 게 낫습니다. 멀쩡한 코드를 try/catch로 뭉뚱그려 감싸면 버그를 처리하는 게 아니라 숨기는 꼴이 됩니다.

try/catch로 비동기 에러도 잡을 수 있나요?

try 블록 안에서 await로 프로미스를 기다릴 때만 잡힙니다. 그냥 somePromise()처럼 호출만 해두면 에러는 잡히지 않고 unhandled rejection으로 빠져버려요. async/await을 쓰면 동기 코드와 완전히 똑같은 감각으로 try/catch를 쓸 수 있고, 프로미스 체인을 그대로 쓴다면 .catch()로 처리하는 게 맞습니다.

자바스크립트에서 에러를 다시 던지려면 어떻게 하나요?

catch 안에서 throw err;만 써주면 됩니다. 원래 에러를 감싼 새 에러를 던져도 되고요. 일부 에러는 내가 처리하고 나머지는 상위로 넘겨야 할 때 유용합니다. 에러의 타입이나 메시지를 먼저 확인해서 처리할 수 있는 건 처리하고, 아닌 건 다시 던져서 호출한 쪽에서도 그 에러를 볼 수 있게 해주세요.

Coddy로 코딩 배우기

시작하기