프로그램 실행 중 발생하는 오류를 적절히 처리하여 프로그램이 중단되지 않도록 하는 방법입니다.
예외 처리(Exception Handling)는 프로그래밍 언어가 정해놓은 규칙에서 벗어난 오류가 발생했을 때의 행동을 정의하는 것입니다.
// ❌ 예외 처리 없는 코드
function divide(a, b) {
return a / b;
}
console.log(divide(10, 0)); // Infinity (수학적으로는 문제)
console.log(divide(10, "hello")); // NaN (예상치 못한 동작)
// ❌ 오류로 프로그램 중단
const user = null;
console.log(user.name); // TypeError: Cannot read property 'name' of null
// 이후 코드는 실행되지 않음!
// ✅ 예외 처리가 있는 코드
function safeDivide(a, b) {
try {
if (b === 0) {
throw new Error("0으로 나눌 수 없습니다");
}
if (typeof a !== "number" || typeof b !== "number") {
throw new Error("숫자만 입력 가능합니다");
}
return a / b;
} catch (error) {
console.error("오류 발생:", error.message);
return null;
}
}
console.log(safeDivide(10, 2)); // 5
console.log(safeDivide(10, 0)); // 오류 발생: 0으로 나눌 수 없습니다
console.log(safeDivide(10, "hello")); // 오류 발생: 숫자만 입력 가능합니다
try {
// 오류가 발생할 가능성이 있는 코드
const result = riskyOperation();
console.log(result);
} catch (error) {
// 오류가 발생했을 때 실행될 코드
console.error("오류가 발생했습니다:", error.message);
}
// 1. JSON 파싱
function parseJSON(jsonString) {
try {
const data = JSON.parse(jsonString);
return { success: true, data };
} catch (error) {
return {
success: false,
error: "유효하지 않은 JSON 형식입니다"
};
}
}
console.log(parseJSON('{"name": "Alice"}'));
// { success: true, data: { name: "Alice" } }
console.log(parseJSON('{invalid json}'));
// { success: false, error: "유효하지 않은 JSON 형식입니다" }
// 2. 배열 접근
function getArrayElement(arr, index) {
try {
if (!Array.isArray(arr)) {
throw new Error("배열이 아닙니다");
}
if (index < 0 || index >= arr.length) {
throw new Error("인덱스가 범위를 벗어났습니다");
}
return arr[index];
} catch (error) {
console.error("오류:", error.message);
return undefined;
}
}
const numbers = [1, 2, 3, 4, 5];
console.log(getArrayElement(numbers, 2)); // 3
console.log(getArrayElement(numbers, 10)); // 오류: 인덱스가 범위를 벗어났습니다
console.log(getArrayElement("not array", 0)); // 오류: 배열이 아닙니다
finally 블록은 try 블록의 성공/실패 여부와 상관없이 항상 실행됩니다.
function demonstrateFinally() {
console.log("1. 시작");
try {
console.log("2. try 블록 실행");
// throw new Error("의도적 오류"); // 주석 해제하면 catch 실행
console.log("3. try 블록 완료");
return "success";
} catch (error) {
console.log("4. catch 블록 실행");
return "error";
} finally {
console.log("5. finally 블록 실행 (항상 실행!)");
}
console.log("6. 함수 끝"); // 도달하지 않음 (return 때문)
}
demonstrateFinally();
/*
출력:
1. 시작
2. try 블록 실행
3. try 블록 완료
4. finally 블록 실행 (항상 실행!)
*/
// 파일 작업
function readFile(filePath) {
let file = null;
try {
file = openFile(filePath); // 파일 열기
const content = file.read();
console.log("파일 내용:", content);
return content;
} catch (error) {
console.error("파일 읽기 오류:", error.message);
return null;
} finally {
// 오류 발생 여부와 관계없이 파일 닫기
if (file) {
file.close();
console.log("파일이 닫혔습니다.");
}
}
}
// 데이터베이스 연결
async function queryDatabase(query) {
let connection = null;
try {
connection = await connectToDatabase();
const result = await connection.execute(query);
return result;
} catch (error) {
console.error("쿼리 실행 오류:", error);
throw error;
} finally {
// 연결 종료 (항상 실행)
if (connection) {
await connection.close();
console.log("데이터베이스 연결이 종료되었습니다.");
}
}
}
// React 예시
async function fetchData() {
let isLoading = true;
try {
console.log("로딩 시작...");
const response = await fetch("/api/data");
const data = await response.json();
return data;
} catch (error) {
console.error("데이터 가져오기 실패:", error);
return null;
} finally {
// 성공/실패 여부와 관계없이 로딩 종료
isLoading = false;
console.log("로딩 완료");
}
}
function processWithTimeout(task, timeout = 5000) {
let timerId = null;
try {
// 타임아웃 설정
timerId = setTimeout(() => {
throw new Error("작업 시간 초과");
}, timeout);
// 작업 실행
const result = task();
console.log("작업 완료:", result);
return result;
} catch (error) {
console.error("오류 발생:", error.message);
return null;
} finally {
// 타이머 정리 (항상 실행)
if (timerId) {
clearTimeout(timerId);
console.log("타이머 정리 완료");
}
}
}
// 사용자 입력을 숫자로 변환하는 함수
function parseNumber(input) {
const number = parseFloat(input);
if (isNaN(number)) {
throw new Error("입력 값이 숫자가 아닙니다.");
}
return number;
}
// 두 숫자를 더하는 함수
function addNumbers(a, b) {
return a + b;
}
// 메인 함수
function calculator() {
console.log("=== 계산기 시작 ===");
try {
const input1 = "10";
const input2 = "20";
console.log(`입력값: ${input1}, ${input2}`);
const number1 = parseNumber(input1);
const number2 = parseNumber(input2);
const result = addNumbers(number1, number2);
console.log(`결과: ${result}`);
return result;
} catch (error) {
console.error(`오류가 발생했습니다: ${error.message}`);
return null;
} finally {
console.log("=== 계산기 종료 ===");
}
}
calculator();
/*
출력:
=== 계산기 시작 ===
입력값: 10, 20
결과: 30
=== 계산기 종료 ===
*/
// 오류 발생 케이스
function calculatorWithError() {
console.log("=== 계산기 시작 ===");
try {
const input1 = "10";
const input2 = "abc"; // 잘못된 입력
console.log(`입력값: ${input1}, ${input2}`);
const number1 = parseNumber(input1);
const number2 = parseNumber(input2); // 여기서 오류 발생
const result = addNumbers(number1, number2);
console.log(`결과: ${result}`);
} catch (error) {
console.error(`오류가 발생했습니다: ${error.message}`);
} finally {
console.log("=== 계산기 종료 ===");
}
}
calculatorWithError();
/*
출력:
=== 계산기 시작 ===
입력값: 10, abc
오류가 발생했습니다: 입력 값이 숫자가 아닙니다.
=== 계산기 종료 ===
*/
throw 문은 사용자 정의 예외를 발생시켜 상위 호출 스택으로 전달합니다.
function validateAge(age) {
if (age < 0) {
throw new Error("나이는 음수일 수 없습니다");
}
if (age > 150) {
throw new Error("나이가 너무 큽니다");
}
return true;
}
function createUser(name, age) {
try {
validateAge(age); // 예외가 발생하면 catch로 전달
return { name, age, createdAt: new Date() };
} catch (error) {
console.error("사용자 생성 실패:", error.message);
return null;
}
}
console.log(createUser("Alice", 25)); // { name: "Alice", age: 25, ... }
console.log(createUser("Bob", -5)); // 사용자 생성 실패: 나이는 음수일 수 없습니다
console.log(createUser("Charlie", 200)); // 사용자 생성 실패: 나이가 너무 큽니다
// 최하위 함수
function readConfig() {
throw new Error("설정 파일을 찾을 수 없습니다");
}
// 중간 함수
function initializeApp() {
try {
const config = readConfig(); // 예외 발생
return config;
} catch (error) {
// 예외를 상위로 다시 전달 (재throw)
throw new Error(`앱 초기화 실패: ${error.message}`);
}
}
// 최상위 함수
function startApp() {
try {
initializeApp();
console.log("앱이 시작되었습니다");
} catch (error) {
console.error("치명적 오류:", error.message);
// 치명적 오류: 앱 초기화 실패: 설정 파일을 찾을 수 없습니다
}
}
startApp();
function validateEmail(email) {
if (!email) {
throw new Error("이메일을 입력해주세요");
}
if (!email.includes("@")) {
throw new Error("올바른 이메일 형식이 아닙니다");
}
return true;
}
function validatePassword(password) {
if (!password) {
throw new Error("비밀번호를 입력해주세요");
}
if (password.length < 8) {
throw new Error("비밀번호는 8자 이상이어야 합니다");
}
return true;
}
function register(email, password) {
try {
console.log("회원가입 시도...");
validateEmail(email);
validatePassword(password);
console.log("회원가입 성공! ✅");
return { success: true, email };
} catch (error) {
console.error("회원가입 실패:", error.message);
return { success: false, error: error.message };
}
}
// 테스트
register("alice@example.com", "securepass123"); // 성공
register("", "password"); // 실패: 이메일을 입력해주세요
register("invalid-email", "password"); // 실패: 올바른 이메일 형식이 아닙니다
register("bob@example.com", "short"); // 실패: 비밀번호는 8자 이상이어야 합니다
function innerFunction() {
throw new Error("내부 함수에서 오류 발생");
}
function middleFunction() {
innerFunction(); // 예외를 상위로 전달
}
function outerFunction() {
try {
middleFunction();
} catch (error) {
console.error("오류 처리:", error.message);
console.error("스택 추적:");
console.error(error.stack); // 어디서 오류가 발생했는지 확인
/*
출력:
오류 처리: 내부 함수에서 오류 발생
스택 추적:
Error: 내부 함수에서 오류 발생
at innerFunction (file.js:2:11)
at middleFunction (file.js:6:5)
at outerFunction (file.js:10:9)
*/
}
}
outerFunction();
에러 타입을 구분하여 각각 다르게 처리할 수 있습니다.
// 1. 기본 커스텀 에러
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;
}
}
class DatabaseError extends Error {
constructor(message, query) {
super(message);
this.name = "DatabaseError";
this.query = query;
}
}
// 2. 에러 타입별 처리
function processData(data) {
try {
if (!data) {
throw new ValidationError("데이터가 없습니다");
}
if (!data.id) {
throw new ValidationError("ID가 필요합니다");
}
// 네트워크 요청 시뮬레이션
if (Math.random() < 0.3) {
throw new NetworkError("서버 연결 실패", 500);
}
// 데이터베이스 작업 시뮬레이션
if (Math.random() < 0.2) {
throw new DatabaseError(
"쿼리 실행 실패",
"SELECT * FROM users"
);
}
return { success: true, data };
} catch (error) {
// 에러 타입별로 다른 처리
if (error instanceof ValidationError) {
console.error("❌ 검증 오류:", error.message);
return { success: false, type: "validation" };
} else if (error instanceof NetworkError) {
console.error("🌐 네트워크 오류:", error.message);
console.error("상태 코드:", error.statusCode);
return { success: false, type: "network" };
} else if (error instanceof DatabaseError) {
console.error("💾 데이터베이스 오류:", error.message);
console.error("쿼리:", error.query);
return { success: false, type: "database" };
} else {
console.error("⚠️ 알 수 없는 오류:", error);
return { success: false, type: "unknown" };
}
}
}
// 테스트
processData({ id: 1, name: "Alice" });
processData(null);
processData({ name: "Bob" });
class APIError extends Error {
constructor(message, statusCode, endpoint) {
super(message);
this.name = "APIError";
this.statusCode = statusCode;
this.endpoint = endpoint;
this.timestamp = new Date();
}
}
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new APIError(
"사용자 정보를 가져올 수 없습니다",
response.status,
`/api/users/${userId}`
);
}
const data = await response.json();
return { success: true, data };
} catch (error) {
if (error instanceof APIError) {
console.error("API 오류:", {
message: error.message,
status: error.statusCode,
endpoint: error.endpoint,
time: error.timestamp
});
// 상태 코드별 처리
if (error.statusCode === 404) {
return { success: false, error: "사용자를 찾을 수 없습니다" };
} else if (error.statusCode === 401) {
return { success: false, error: "인증이 필요합니다" };
} else if (error.statusCode >= 500) {
return { success: false, error: "서버 오류가 발생했습니다" };
}
}
return { success: false, error: "알 수 없는 오류" };
}
}
// ✅ async/await과 try-catch
async function fetchData(url) {
try {
console.log("데이터 요청 중...");
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP 오류! 상태: ${response.status}`);
}
const data = await response.json();
console.log("데이터 받기 성공:", data);
return data;
} catch (error) {
console.error("데이터 가져오기 실패:", error.message);
return null;
} finally {
console.log("요청 완료");
}
}
// 사용
async function loadUserProfile(userId) {
const userData = await fetchData(`/api/users/${userId}`);
if (userData) {
console.log("사용자 프로필:", userData);
} else {
console.log("기본 프로필 사용");
}
}
// Promise 체인에서의 에러 처리
fetch("/api/data")
.then(response => {
if (!response.ok) {
throw new Error("네트워크 응답이 올바르지 않습니다");
}
return response.json();
})
.then(data => {
console.log("데이터:", data);
return processData(data);
})
.catch(error => {
console.error("오류 발생:", error.message);
})
.finally(() => {
console.log("작업 완료");
});
async function downloadCSV(queryParams) {
try {
console.log("CSV 다운로드 시작...");
// API 호출
const response = await fetch(
`/api/common/openData/getExmnStatExcelByFile?${makeUrlQuery(queryParams)}`
);
// 응답 상태 확인
if (!response.ok) {
throw new Error(`서버 오류: ${response.status}`);
}
// JSON 데이터 파싱
const data = await response.json();
// 데이터 검증
if (!data || !data.length) {
throw new Error("다운로드할 데이터가 없습니다");
}
// CSV 헤더 생성
const headerString = "조사통계종류,기준연도,자료명,조회수,작성일,수정일";
// CSV 데이터 변환
const csvRows = data.map(item => {
return [
JSON.stringify(item.exmnStatCtgrNm),
JSON.stringify(item.crtrYr),
JSON.stringify(item.title),
JSON.stringify(item.inqCnt),
item.regDt ? JSON.stringify(item.regDt) : "-",
item.mdfcnDt ? JSON.stringify(item.mdfcnDt) : "-"
].join(",");
});
// CSV 다운로드 실행
clientCSVDownload(
headerString,
csvRows,
`우리플랫폼_조사통계.csv`
);
console.log("CSV 다운로드 완료! ✅");
return { success: true };
} catch (error) {
console.error("CSV 다운로드 실패:", error.message);
// 사용자에게 알림
alert(`다운로드 실패: ${error.message}`);
return { success: false, error: error.message };
} finally {
console.log("다운로드 프로세스 종료");
}
}
// 버튼 클릭 핸들러
const handleDownload = async () => {
const { page, size, orderBy, sort, ...rest } = queryInstance;
await downloadCSV(rest);
};
async function fetchMultipleData() {
try {
console.log("여러 데이터 요청 시작...");
// Promise.all: 모든 요청이 성공해야 함
const [users, posts, comments] = await Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json()),
fetch("/api/comments").then(r => r.json())
]);
console.log("모든 데이터 로드 성공!");
return { users, posts, comments };
} catch (error) {
console.error("데이터 로드 실패:", error);
return null;
}
}
async function fetchMultipleDataSafely() {
try {
console.log("여러 데이터 요청 시작 (안전 모드)...");
// Promise.allSettled: 일부 실패해도 계속 진행
const results = await Promise.allSettled([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json()),
fetch("/api/comments").then(r => r.json())
]);
// 결과 처리
const [usersResult, postsResult, commentsResult] = results;
return {
users: usersResult.status === "fulfilled" ? usersResult.value : [],
posts: postsResult.status === "fulfilled" ? postsResult.value : [],
comments: commentsResult.status === "fulfilled" ? commentsResult.value : []
};
} catch (error) {
console.error("예상치 못한 오류:", error);
return { users: [], posts: [], comments: [] };
}
}
// ❌ 나쁜 예
throw new Error("오류");
// ✅ 좋은 예
throw new Error("사용자 ID가 유효하지 않습니다: " + userId);
// ❌ 모든 에러를 동일하게 처리
try {
// ...
} catch (error) {
console.error("오류 발생");
}
// ✅ 에러 타입별로 다르게 처리
try {
// ...
} catch (error) {
if (error instanceof ValidationError) {
handleValidationError(error);
} else if (error instanceof NetworkError) {
handleNetworkError(error);
} else {
handleUnknownError(error);
}
}
// ❌ 과도한 try-catch
function processUser(user) {
try {
const name = user.name;
} catch (error) {
// 불필요
}
try {
const age = user.age;
} catch (error) {
// 불필요
}
}
// ✅ 적절한 범위
function processUser(user) {
try {
validateUser(user);
const result = transformUser(user);
saveUser(result);
} catch (error) {
handleUserProcessingError(error);
}
}
// ✅ 에러 정보를 상세히 기록
function logError(error, context = {}) {
console.error({
message: error.message,
name: error.name,
stack: error.stack,
timestamp: new Date().toISOString(),
...context
});
}
try {
// 위험한 작업
riskyOperation();
} catch (error) {
logError(error, {
operation: "riskyOperation",
userId: currentUser.id,
params: { /* ... */ }
});
}
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error("Request failed");
return await response.json();
} catch (error) {
console.log(`시도 ${i + 1}/${maxRetries} 실패`);
if (i === maxRetries - 1) {
// 마지막 재시도도 실패
throw new Error(`${maxRetries}번 시도 후 실패: ${error.message}`);
}
// 재시도 전 대기 (Exponential Backoff)
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, i) * 1000)
);
}
}
}
// 사용
fetchWithRetry("/api/data")
.then(data => console.log("데이터:", data))
.catch(error => console.error("최종 실패:", error));
// 커스텀 에러 정의
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class DuplicateError extends Error {
constructor(message, field, value) {
super(message);
this.name = "DuplicateError";
this.field = field;
this.value = value;
}
}
// 유효성 검증 함수들
function validateEmail(email) {
if (!email) {
throw new ValidationError("이메일을 입력해주세요", "email");
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new ValidationError("올바른 이메일 형식이 아닙니다", "email");
}
return true;
}
function validatePassword(password) {
if (!password) {
throw new ValidationError("비밀번호를 입력해주세요", "password");
}
if (password.length < 8) {
throw new ValidationError(
"비밀번호는 8자 이상이어야 합니다",
"password"
);
}
if (!/[A-Z]/.test(password)) {
throw new ValidationError(
"비밀번호에 대문자가 포함되어야 합니다",
"password"
);
}
if (!/[0-9]/.test(password)) {
throw new ValidationError(
"비밀번호에 숫자가 포함되어야 합니다",
"password"
);
}
return true;
}
function validateAge(age) {
if (age === undefined || age === null) {
throw new ValidationError("나이를 입력해주세요", "age");
}
if (typeof age !== "number" || isNaN(age)) {
throw new ValidationError("나이는 숫자여야 합니다", "age");
}
if (age < 0) {
throw new ValidationError("나이는 0 이상이어야 합니다", "age");
}
if (age > 150) {
throw new ValidationError("올바른 나이를 입력해주세요", "age");
}
return true;
}
// 중복 체크 (시뮬레이션)
async function checkDuplicateEmail(email) {
// 실제로는 데이터베이스 조회
const existingEmails = ["test@example.com", "admin@example.com"];
if (existingEmails.includes(email)) {
throw new DuplicateError(
"이미 사용 중인 이메일입니다",
"email",
email
);
}
return true;
}
// 사용자 등록 함수
async function registerUser(userData) {
console.log("=".repeat(50));
console.log("회원가입 시작");
console.log("=".repeat(50));
try {
const { email, password, age, name } = userData;
// 1단계: 기본 유효성 검증
console.log("1️⃣ 유효성 검증 중...");
validateEmail(email);
validatePassword(password);
validateAge(age);
console.log("✅ 유효성 검증 통과");
// 2단계: 중복 체크
console.log("2️⃣ 중복 체크 중...");
await checkDuplicateEmail(email);
console.log("✅ 중복 체크 통과");
// 3단계: 사용자 생성 (시뮬레이션)
console.log("3️⃣ 사용자 생성 중...");
const newUser = {
id: Date.now(),
email,
name,
age,
createdAt: new Date().toISOString()
};
console.log("✅ 사용자 생성 완료");
// 4단계: 환영 이메일 발송 (시뮬레이션)
console.log("4️⃣ 환영 이메일 발송 중...");
await sendWelcomeEmail(email, name);
console.log("✅ 환영 이메일 발송 완료");
console.log("=".repeat(50));
console.log("🎉 회원가입 성공!");
console.log("=".repeat(50));
return {
success: true,
user: newUser,
message: "회원가입이 완료되었습니다"
};
} catch (error) {
console.log("=".repeat(50));
if (error instanceof ValidationError) {
console.error("❌ 유효성 검증 실패");
console.error(`필드: ${error.field}`);
console.error(`메시지: ${error.message}`);
return {
success: false,
type: "validation",
field: error.field,
message: error.message
};
} else if (error instanceof DuplicateError) {
console.error("❌ 중복 데이터 발견");
console.error(`필드: ${error.field}`);
console.error(`값: ${error.value}`);
console.error(`메시지: ${error.message}`);
return {
success: false,
type: "duplicate",
field: error.field,
message: error.message
};
} else {
console.error("⚠️ 예상치 못한 오류");
console.error(error);
return {
success: false,
type: "unknown",
message: "회원가입 중 오류가 발생했습니다"
};
}
} finally {
console.log("=".repeat(50));
console.log("회원가입 프로세스 종료");
console.log("=".repeat(50));
}
}
// 환영 이메일 발송 (시뮬레이션)
async function sendWelcomeEmail(email, name) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`📧 ${name}님께 환영 이메일 발송: ${email}`);
resolve();
}, 1000);
});
}
// 테스트 케이스
async function runTests() {
// ✅ 성공 케이스
console.log("\n📝 테스트 1: 정상 회원가입");
await registerUser({
email: "alice@example.com",
password: "SecurePass123",
age: 25,
name: "Alice"
});
// ❌ 이메일 유효성 실패
console.log("\n📝 테스트 2: 잘못된 이메일");
await registerUser({
email: "invalid-email",
password: "SecurePass123",
age: 25,
name: "Bob"
});
// ❌ 비밀번호 유효성 실패
console.log("\n📝 테스트 3: 약한 비밀번호");
await registerUser({
email: "bob@example.com",
password: "weak",
age: 30,
name: "Bob"
});
// ❌ 나이 유효성 실패
console.log("\n📝 테스트 4: 잘못된 나이");
await registerUser({
email: "charlie@example.com",
password: "SecurePass123",
age: -5,
name: "Charlie"
});
// ❌ 중복 이메일
console.log("\n📝 테스트 5: 중복 이메일");
await registerUser({
email: "test@example.com",
password: "SecurePass123",
age: 28,
name: "Test"
});
}
// 테스트 실행
runTests();
class OrderError extends Error {
constructor(message, code, details = {}) {
super(message);
this.name = "OrderError";
this.code = code;
this.details = details;
}
}
class Order {
constructor() {
this.items = [];
this.status = "pending";
}
// 상품 추가
addItem(productId, quantity, price) {
try {
// 유효성 검증
if (!productId) {
throw new OrderError(
"상품 ID가 필요합니다",
"INVALID_PRODUCT_ID"
);
}
if (!quantity || quantity <= 0) {
throw new OrderError(
"수량은 1개 이상이어야 합니다",
"INVALID_QUANTITY",
{ quantity }
);
}
if (!price || price < 0) {
throw new OrderError(
"올바른 가격이 아닙니다",
"INVALID_PRICE",
{ price }
);
}
// 재고 확인 (시뮬레이션)
const stock = this.checkStock(productId);
if (stock < quantity) {
throw new OrderError(
"재고가 부족합니다",
"OUT_OF_STOCK",
{ productId, requested: quantity, available: stock }
);
}
// 상품 추가
this.items.push({ productId, quantity, price });
console.log(`✅ 상품 추가: ${productId} x ${quantity}`);
return { success: true };
} catch (error) {
if (error instanceof OrderError) {
console.error(`❌ 상품 추가 실패 [${error.code}]:`, error.message);
if (Object.keys(error.details).length > 0) {
console.error("상세 정보:", error.details);
}
} else {
console.error("❌ 예상치 못한 오류:", error);
}
return { success: false, error: error.message };
}
}
// 재고 확인 (시뮬레이션)
checkStock(productId) {
const stocks = {
"P001": 10,
"P002": 5,
"P003": 0,
"P004": 100
};
return stocks[productId] ?? 0;
}
// 총 금액 계산
getTotal() {
return this.items.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
}
// 주문 완료
async checkout(paymentInfo) {
console.log("\n" + "=".repeat(50));
console.log("주문 처리 시작");
console.log("=".repeat(50));
try {
// 1. 장바구니 검증
console.log("1️⃣ 장바구니 검증 중...");
if (this.items.length === 0) {
throw new OrderError(
"장바구니가 비어있습니다",
"EMPTY_CART"
);
}
console.log("✅ 장바구니 검증 완료");
// 2. 결제 정보 검증
console.log("2️⃣ 결제 정보 검증 중...");
this.validatePaymentInfo(paymentInfo);
console.log("✅ 결제 정보 검증 완료");
// 3. 재고 재확인
console.log("3️⃣ 재고 재확인 중...");
for (const item of this.items) {
const stock = this.checkStock(item.productId);
if (stock < item.quantity) {
throw new OrderError(
"주문 처리 중 재고가 소진되었습니다",
"STOCK_DEPLETED",
{ productId: item.productId }
);
}
}
console.log("✅ 재고 확인 완료");
// 4. 결제 처리
console.log("4️⃣ 결제 처리 중...");
await this.processPayment(paymentInfo, this.getTotal());
console.log("✅ 결제 완료");
// 5. 재고 차감
console.log("5️⃣ 재고 차감 중...");
this.reduceStock();
console.log("✅ 재고 차감 완료");
// 6. 주문 확정
this.status = "completed";
const orderId = `ORD-${Date.now()}`;
console.log("=".repeat(50));
console.log("🎉 주문 완료!");
console.log(`주문 번호: ${orderId}`);
console.log(`총 금액: ${this.getTotal().toLocaleString()}원`);
console.log("=".repeat(50));
return {
success: true,
orderId,
total: this.getTotal(),
items: this.items
};
} catch (error) {
this.status = "failed";
console.log("=".repeat(50));
console.error("❌ 주문 실패");
if (error instanceof OrderError) {
console.error(`오류 코드: ${error.code}`);
console.error(`메시지: ${error.message}`);
if (Object.keys(error.details).length > 0) {
console.error("상세:", error.details);
}
} else {
console.error("예상치 못한 오류:", error);
}
console.log("=".repeat(50));
return {
success: false,
error: error.message,
code: error.code
};
} finally {
console.log("주문 처리 프로세스 종료\n");
}
}
// 결제 정보 검증
validatePaymentInfo(paymentInfo) {
if (!paymentInfo) {
throw new OrderError(
"결제 정보가 필요합니다",
"MISSING_PAYMENT_INFO"
);
}
const { cardNumber, cvv, expiry } = paymentInfo;
if (!cardNumber || cardNumber.length !== 16) {
throw new OrderError(
"올바른 카드 번호를 입력해주세요",
"INVALID_CARD_NUMBER"
);
}
if (!cvv || cvv.length !== 3) {
throw new OrderError(
"올바른 CVV를 입력해주세요",
"INVALID_CVV"
);
}
if (!expiry) {
throw new OrderError(
"카드 만료일을 입력해주세요",
"MISSING_EXPIRY"
);
}
return true;
}
// 결제 처리 (시뮬레이션)
async processPayment(paymentInfo, amount) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 10% 확률로 결제 실패 시뮬레이션
if (Math.random() < 0.1) {
reject(new OrderError(
"결제 처리 실패",
"PAYMENT_FAILED",
{ amount }
));
} else {
resolve();
}
}, 1000);
});
}
// 재고 차감 (시뮬레이션)
reduceStock() {
this.items.forEach(item => {
console.log(` 📦 ${item.productId} 재고 차감: ${item.quantity}개`);
});
}
}
// 테스트
async function testOrderSystem() {
const order = new Order();
// 상품 추가
order.addItem("P001", 2, 10000);
order.addItem("P002", 1, 20000);
order.addItem("P003", 1, 15000); // 재고 없음
order.addItem("P004", 3, 5000);
// 주문 완료 시도
const result = await order.checkout({
cardNumber: "1234567812345678",
cvv: "123",
expiry: "12/25"
});
console.log("최종 결과:", result);
}
testOrderSystem();
| 구문 | 역할 | 사용 시점 |
|---|---|---|
| try | 오류 가능성 있는 코드 실행 | 예외 발생 가능 영역 |
| catch | 오류 발생 시 처리 | 예외 처리 로직 |
| finally | 항상 실행 | 리소스 정리, 로깅 |
| throw | 예외 발생 | 사용자 정의 오류 |
구체적인 에러 메시지 작성
throw new Error(`사용자 ID ${userId}를 찾을 수 없습니다`);
적절한 에러 타입 사용
class ValidationError extends Error { }
class NetworkError extends Error { }
finally로 리소스 정리
finally {
connection.close();
clearTimeout(timer);
}
에러 로깅
console.error({
message: error.message,
stack: error.stack,
context: { userId, action }
});
재시도 로직 구현
async function fetchWithRetry(url, maxRetries = 3) {
// 재시도 로직
}
빈 catch 블록
// ❌ 절대 하지 마세요!
try {
riskyOperation();
} catch (error) {
// 아무것도 안 함
}
모든 에러를 똑같이 처리
// ❌ 나쁜 예
catch (error) {
alert("오류 발생");
}
과도한 try-catch
// ❌ 불필요한 중첩
try {
try {
try {
// ...
} catch (e3) { }
} catch (e2) { }
} catch (e1) { }
async function apiCall(endpoint, options = {}) {
try {
const response = await fetch(endpoint, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error(`API 호출 실패 [${endpoint}]:`, error);
throw error;
}
}
function validateForm(formData) {
const errors = {};
try {
validateEmail(formData.email);
} catch (error) {
errors.email = error.message;
}
try {
validatePassword(formData.password);
} catch (error) {
errors.password = error.message;
}
return {
isValid: Object.keys(errors).length === 0,
errors
};
}
async function performAsyncTask() {
let resource = null;
try {
resource = await acquireResource();
await processResource(resource);
} catch (error) {
await handleError(error);
} finally {
if (resource) {
await releaseResource(resource);
}
}
}