Skip to Content
BlogJavaScript 정리기초 정리에러 처리 (try/catch/finally)

에러 처리 (try/catch/finally)

try / catch / finally

예상치 못한 에러가 발생해도 프로그램 전체가 멈추지 않도록, 에러가 날 수 있는 코드를 감싸서 처리하는 문법이다.

기본 사용법

function readData() { try { console.log("데이터 읽는 중..."); const user = { name: "gori" }; console.log(user.address.city); // address가 없어서 에러 발생 } catch (error) { console.log("에러 처리:", error.message); } finally { console.log("리소스 정리"); // 성공하든 실패하든 반드시 실행됨 } } readData(); // 데이터 읽는 중... // 에러 처리: Cannot read properties of undefined (reading 'city') // 리소스 정리
  • try 블록 안에서 에러가 발생하면, 그 즉시 실행이 멈추고 catch 블록으로 넘어간다.
  • catch 블록에서 받는 매개변수(error)에는 발생한 에러 정보가 담긴다. 에러가 없으면 catch 블록은 아예 실행되지 않는다.
  • finally 블록은 에러 발생 여부와 상관없이, try나 catch가 끝난 뒤 항상 실행된다. 파일 닫기, 로딩 상태 해제처럼 성공/실패와 무관하게 반드시 처리해야 하는 마무리 작업을 여기에 넣는다.

throw — 직접 에러를 발생시키기

특정 조건에서 의도적으로 에러를 던지고 싶을 때 사용한다.

function getUser(id) { if (!id) { throw new Error("id는 필수입니다"); } return { id, name: "gori" }; } try { const user = getUser(); } catch (error) { console.log(error.message); // id는 필수입니다 }

중첩된 함수에서도 catch로 전달됨

에러는 발생한 지점부터 호출 스택을 따라 위로 전파된다. 어느 한 곳에서라도 catch로 잡아주면 그 이후 코드는 정상적으로 계속 실행된다.

function step3() { throw new Error("3단계에서 실패"); } function step2() { step3(); } function step1() { step2(); } try { step1(); } catch (error) { console.log("잡은 위치:", error.message); // 잡은 위치: 3단계에서 실패 }

비동기 코드에서의 에러 처리

try...catch는 동기 코드에만 적용된다. setTimeout 콜백 안에서 발생한 에러는 바깥의 try...catch로 잡히지 않는다.

try { setTimeout(() => { throw new Error("타이머 안에서 발생한 에러"); }, 100); } catch (error) { console.log("여기선 안 잡힘"); // 실행되지 않음 }

반면 async/await와 함께 쓰면 비동기 코드도 동기 코드처럼 try...catch로 잡을 수 있다.

async function fetchUser() { try { const response = await fetch("https://api.example.com/users/1"); const data = await response.json(); console.log(data); } catch (error) { console.log("요청 실패:", error.message); // 정상적으로 잡힘 } }


에러 객체 (Error Object)

throw로 아무 값이나 던질 수는 있지만, 관례상 Error 객체(또는 그 하위 타입)를 던지는 게 일반적이다. Error 객체는 에러에 대한 정보를 표준화된 형태로 담고 있기 때문이다.

const error = new Error("문제가 발생했습니다"); console.log(error.message); // "문제가 발생했습니다" console.log(error.name); // "Error" console.log(error.stack); // 에러가 발생한 위치를 추적할 수 있는 스택 정보
속성설명
message에러에 대한 설명 메시지
name에러의 종류 (기본값은 “Error”)
stack에러가 어디서 발생했는지 추적하는 호출 스택 정보 (디버깅에 유용)

내장 에러 타입들*

JS는 상황별로 미리 정의된 여러 에러 타입을 제공한다. 모두 Error를 상속받는다.

// TypeError: 값의 타입이 예상과 다를 때 null.toString(); // TypeError: Cannot read properties of null // ReferenceError: 선언되지 않은 변수를 참조할 때 console.log(notDefined); // ReferenceError: notDefined is not defined // RangeError: 값의 범위를 벗어났을 때 new Array(-1); // RangeError: Invalid array length // SyntaxError: 문법 오류 (주로 JSON.parse 등에서 발생) JSON.parse("{ invalid json }"); // SyntaxError

catch에서 error.name이나 error instanceof TypeError처럼 에러 종류를 구분해서 다르게 처리할 수도 있다.

try { JSON.parse("{ invalid json }"); } catch (error) { if (error instanceof SyntaxError) { console.log("JSON 형식이 잘못됐습니다"); } else { console.log("알 수 없는 에러:", error.message); } }


커스텀 에러 (Custom Error)

프로젝트마다 발생할 수 있는 특수한 에러 상황을 표현하기 위해, Error를 상속받아 직접 에러 타입을 만들 수 있다.

기본 형태

class ValidationError extends Error { constructor(message) { super(message); // 부모(Error)의 constructor 호출 → this.message 설정 this.name = "ValidationError"; // 에러 이름을 구분되게 지정 } } function validateAge(age) { if (age < 0) { throw new ValidationError("나이는 음수가 될 수 없습니다"); } return age; } try { validateAge(-5); } catch (error) { console.log(error.name); // ValidationError console.log(error.message); // 나이는 음수가 될 수 없습니다 console.log(error instanceof Error); // true (여전히 Error의 인스턴스) }

여러 종류의 커스텀 에러를 구분해서 처리하기

class NetworkError extends Error { constructor(message, statusCode) { super(message); this.name = "NetworkError"; this.statusCode = statusCode; } } class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } } async function getUser(id) { if (!id) { throw new ValidationError("id는 필수입니다"); } const response = await fetch(`https://api.example.com/users/${id}`); if (!response.ok) { throw new NetworkError("사용자 조회 실패", response.status); } return await response.json(); } async function handleGetUser(id) { try { const user = await getUser(id); console.log(user); } catch (error) { if (error instanceof ValidationError) { console.log("입력값 오류:", error.message); } else if (error instanceof NetworkError) { console.log(`네트워크 오류 (${error.statusCode}):`, error.message); } else { console.log("알 수 없는 오류:", error.message); } } }

이렇게 에러 타입을 구분해두면, 에러의 원인에 따라 사용자에게 보여줄 메시지나 후속 처리를 다르게 분기할 수 있어서 실무에서 특히 유용하다. 예를 들어 ValidationError는 입력값을 다시 확인하라는 메시지를, NetworkError는 statusCode에 따라 재시도 로직을 붙이는 식으로 활용할 수 있다.



마지막 수정