에러 처리 (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 }"); // SyntaxErrorcatch에서 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에 따라 재시도 로직을 붙이는 식으로 활용할 수 있다.