JavaScript 에러 처리 | try-catch, Error 객체, 커스텀 에러
이 글의 핵심
JavaScript 에러 처리에 대한 실전 가이드입니다. try-catch, Error 객체, 커스텀 에러 등을 예제와 함께 설명합니다.
들어가며
에러 처리는 실행 중 실패할 수 있는 코드에서 사용자·로그·복구 경로를 정하는 일입니다. try/catch로 잡을지, Promise의 catch로 이어질지, 에러 타입을 나눌지까지 이 글에서 정리합니다.
1. try-catch-finally
기본 사용법
try {
const result = riskyOperation();
console.log(result);
} catch (error) {
console.error("에러 발생:", error.message);
} finally {
console.log("정리 작업");
}
실전 예제
function divide(a, b) {
if (b === 0) {
throw new Error("0으로 나눌 수 없습니다");
}
return a / b;
}
try {
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // Error!
console.log("이 줄은 실행 안 됨");
} catch (error) {
console.error("에러:", error.message);
} finally {
console.log("계산 완료");
}
중첩 try-catch
try {
try {
throw new Error("내부 에러");
} catch (innerError) {
console.log("내부 처리:", innerError.message);
throw new Error("외부 에러");
}
} catch (outerError) {
console.log("외부 처리:", outerError.message);
}
2. Error 객체
내장 Error 타입
// Error: 기본 에러
throw new Error("일반 에러");
// SyntaxError: 문법 에러
try {
eval("{ invalid json");
} catch (e) {
console.log(e.name); // SyntaxError
}
// ReferenceError: 존재하지 않는 변수
try {
console.log(nonExistent);
} catch (e) {
console.log(e.name); // ReferenceError
}
// TypeError: 타입 에러
try {
null.toString();
} catch (e) {
console.log(e.name); // TypeError
}
// RangeError: 범위 에러
try {
new Array(-1);
} catch (e) {
console.log(e.name); // RangeError
}
Error 객체 속성
try {
throw new Error("테스트 에러");
} catch (error) {
console.log(error.name); // Error
console.log(error.message); // 테스트 에러
console.log(error.stack); // 스택 트레이스
}
3. 커스텀 에러
커스텀 에러 클래스
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = "NetworkError";
this.statusCode = statusCode;
}
}
function validateAge(age) {
if (typeof age !== 'number') {
throw new ValidationError("나이는 숫자여야 합니다");
}
if (age < 0 || age > 150) {
throw new ValidationError("나이는 0-150 사이여야 합니다");
}
return true;
}
try {
validateAge("25");
} catch (error) {
if (error instanceof ValidationError) {
console.error("유효성 에러:", error.message);
} else {
console.error("알 수 없는 에러:", error);
}
}
여러 에러 타입 처리
class DatabaseError extends Error {
constructor(message, query) {
super(message);
this.name = "DatabaseError";
this.query = query;
}
}
function processData(data) {
try {
if (!data) {
throw new ValidationError("데이터가 없습니다");
}
if (data.age < 0) {
throw new ValidationError("나이는 양수여야 합니다");
}
return data;
} catch (error) {
if (error instanceof ValidationError) {
console.error("유효성 에러:", error.message);
} else if (error instanceof DatabaseError) {
console.error("DB 에러:", error.message, error.query);
} else {
console.error("알 수 없는 에러:", error);
}
throw error;
}
}
4. 비동기 에러 처리
Promise 에러
// .catch()
fetch("https://api.example.com/data")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("에러:", error));
// 체인 중간 에러
Promise.resolve(1)
.then(x => {
throw new Error("에러!");
})
.then(x => console.log(x))
.catch(error => console.error(error.message))
.then(() => console.log("복구됨"));
// 여러 Promise
Promise.all([
fetch("/api/users"),
fetch("/api/posts")
])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(data => console.log(data))
.catch(error => console.error("하나라도 실패:", error));
async/await 에러
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new NetworkError(`HTTP ${response.status}`, response.status);
}
const data = await response.json();
return data;
} catch (error) {
console.error("에러:", error.message);
return null;
}
}
async function complexOperation() {
try {
const data = await fetchData();
const result = processData(data);
return result;
} catch (error) {
if (error instanceof NetworkError) {
console.error("네트워크 에러:", error.statusCode);
} else if (error instanceof ValidationError) {
console.error("유효성 에러:", error.message);
} else {
console.error("알 수 없는 에러:", error);
}
throw error;
}
}
5. 실전 패턴
패턴 1: 에러 래퍼
class Result {
constructor(success, data, error) {
this.success = success;
this.data = data;
this.error = error;
}
static ok(data) {
return new Result(true, data, null);
}
static fail(error) {
return new Result(false, null, error);
}
}
async function fetchUserSafe(id) {
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return Result.ok(user);
} catch (error) {
return Result.fail(error.message);
}
}
const result = await fetchUserSafe(1);
if (result.success) {
console.log("데이터:", result.data);
} else {
console.error("에러:", result.error);
}
패턴 2: 재시도
async function retry(fn, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) {
throw error;
}
console.log(`재시도 ${i + 1}/${maxRetries}`);
await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
}
}
}
retry(() => fetch("https://api.example.com/data"))
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("최종 실패:", error));
패턴 3: 에러 로깅
class ErrorLogger {
static log(error, context = {}) {
const errorInfo = {
name: error.name,
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
...context
};
console.error("에러 로그:", JSON.stringify(errorInfo, null, 2));
// 서버로 전송
// fetch('/api/errors', { method: 'POST', body: JSON.stringify(errorInfo) });
}
}
try {
throw new Error("테스트 에러");
} catch (error) {
ErrorLogger.log(error, { userId: 123, action: "데이터 로드" });
}
6. 실전 예제
예제: API 클라이언트
class ApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new NetworkError(
`HTTP ${response.status}: ${response.statusText}`,
response.status
);
}
const data = await response.json();
return Result.ok(data);
} catch (error) {
if (error instanceof NetworkError) {
console.error("네트워크 에러:", error.message);
} else if (error instanceof SyntaxError) {
console.error("JSON 파싱 에러:", error.message);
} else {
console.error("알 수 없는 에러:", error);
}
return Result.fail(error.message);
}
}
async get(endpoint) {
return this.request(endpoint);
}
async post(endpoint, body) {
return this.request(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
}
}
const api = new ApiClient("https://api.example.com");
const result = await api.get("/users/1");
if (result.success) {
console.log("사용자:", result.data);
} else {
console.error("에러:", result.error);
}
정리
핵심 요약
- try-catch-finally: 에러 처리 구조
- throw: 에러 발생
- Error 객체: name, message, stack
- 커스텀 에러: extends Error
- 비동기: .catch() 또는 try-catch (async/await)
에러 처리 팁
- 명확한 에러 메시지: 문제를 쉽게 파악
- 에러 타입 구분: instanceof로 처리 분기
- 재발생: throw error로 상위로 전파
- 로깅: 에러 정보 기록
다음 단계
- JavaScript 디자인 패턴
- JavaScript 비동기
- TypeScript 시작하기
관련 글
- C++ 예외 처리 | try/catch/throw
- Java 예외 처리 | try-catch, throws, 커스텀 예외
- Python 예외 처리 | try-except, raise, 커스텀 예외 완벽 정리
- Swift 에러 처리 | do-catch, throw, Result
- C++ 디버깅 실전 가이드 | gdb, LLDB, Visual Studio 완벽 활용