강력한 오류 처리는 프로덕션 준비가 된 JavaScript와 취약한 데모를 구별합니다. 이 가이드는 try/catch 패턴, 비동기 오류 처리, 사용자 정의 오류 클래스를 다룹니다.
try/catch 기초
try/catch/finally 블록은 JavaScript의 동기 오류 처리의 기반입니다.
// try/catch/finally fundamentals
// Basic structure
try {
const data = JSON.parse(userInput); // Can throw SyntaxError
processData(data);
} catch (error) {
if (error instanceof SyntaxError) {
console.error('Invalid JSON:', error.message);
} else {
throw error; // Re-throw errors you can't handle here
}
} finally {
cleanup(); // Always runs — use for resource cleanup
}
// What catch does NOT catch:
// 1. Errors in async callbacks (use async/await instead)
// 2. Errors in setTimeout/setInterval
// 3. Errors in event handlers
// Wrong: setTimeout error is not caught
try {
setTimeout(() => {
throw new Error('This is NOT caught by outer try/catch');
}, 100);
} catch (e) {
// This never runs
}
// Right: wrap async code
setTimeout(() => {
try {
throw new Error('This IS caught');
} catch (e) {
console.error(e);
}
}, 100);
// Distinguishing error types
function handleError(error) {
if (error instanceof TypeError) {
// Null dereference, wrong type
} else if (error instanceof RangeError) {
// Array out of bounds, recursion limit
} else if (error instanceof NetworkError) {
// Custom: network failure
} else {
// Unknown: re-throw
throw error;
}
}async/await 오류 처리
async 함수는 Promise를 반환하므로 오류는 다르게 잡아야 합니다.
// Async/Await Error Handling
// 1. Basic async error handling
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was aborted');
return null;
}
throw error; // Re-throw for caller to handle
}
}
// 2. Handling multiple async operations
async function loadDashboard(userId) {
// Run in parallel, catch errors individually
const [user, posts, stats] = await Promise.allSettled([
fetchUser(userId),
fetchPosts(userId),
fetchStats(userId),
]);
return {
user: user.status === 'fulfilled' ? user.value : null,
posts: posts.status === 'fulfilled' ? posts.value : [],
stats: stats.status === 'fulfilled' ? stats.value : {},
errors: [user, posts, stats]
.filter(r => r.status === 'rejected')
.map(r => r.reason),
};
}
// 3. Async error handling utility
async function tryCatchAsync<T>(
promise: Promise<T>
): Promise<[T | null, Error | null]> {
try {
const data = await promise;
return [data, null];
} catch (error) {
return [null, error instanceof Error ? error : new Error(String(error))];
}
}
// Usage: avoids nested try/catch
const [user, error] = await tryCatchAsync(fetchUser(id));
if (error) {
console.error('Failed to fetch user:', error.message);
return;
}
console.log(user.name);사용자 정의 오류 클래스
사용자 정의 오류 클래스는 구조화된 오류 처리와 더 나은 스택 추적을 가능하게 합니다.
// Custom Error Classes
class AppError extends Error {
public readonly code: string;
public readonly statusCode: number;
public readonly isOperational: boolean;
constructor(
message: string,
code: string,
statusCode = 500,
isOperational = true
) {
super(message);
this.name = this.constructor.name;
this.code = code;
this.statusCode = statusCode;
this.isOperational = isOperational;
// Maintains proper prototype chain (important for instanceof)
Object.setPrototypeOf(this, new.target.prototype);
// Captures stack trace (V8 only)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
class ValidationError extends AppError {
public readonly fields: Record<string, string[]>;
constructor(message: string, fields: Record<string, string[]> = {}) {
super(message, 'VALIDATION_ERROR', 400);
this.fields = fields;
}
}
class NotFoundError extends AppError {
constructor(resource: string, id: string | number) {
super(`${resource} with id ${id} not found`, 'NOT_FOUND', 404);
}
}
class NetworkError extends AppError {
public readonly url: string;
constructor(message: string, url: string) {
super(message, 'NETWORK_ERROR', 503);
this.url = url;
}
}
// Usage
function findUser(id: number) {
const user = db.find(id);
if (!user) throw new NotFoundError('User', id);
return user;
}
// Error type checking
try {
findUser(999);
} catch (error) {
if (error instanceof NotFoundError) {
res.status(404).json({ error: error.message, code: error.code });
} else if (error instanceof ValidationError) {
res.status(400).json({ error: error.message, fields: error.fields });
} else {
// Unknown error — log and return 500
logger.error('Unexpected error', { error });
res.status(500).json({ error: 'Internal server error' });
}
}전역 오류 처리기
일부 오류는 로컬 try/catch 블록을 통과합니다. 전역 처리기는 마지막 방어선입니다.
// Global Error Handlers
// Browser: uncaught synchronous errors
window.onerror = (message, source, lineno, colno, error) => {
console.error('Uncaught error:', { message, source, lineno, colno, error });
reportToSentry(error);
return true; // Prevents default browser error dialog
};
// Browser: unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
reportToSentry(event.reason);
event.preventDefault(); // Prevents console error
});
// Node.js: uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
reportToSentry(error);
// Exit after logging — cannot safely continue after uncaughtException
process.exit(1);
});
// Node.js: unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection at:', promise, 'reason:', reason);
reportToSentry(reason instanceof Error ? reason : new Error(String(reason)));
});
// Express.js: error middleware (must have 4 params)
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
const isOperational = err.isOperational || false;
if (!isOperational) {
// Unexpected error — log full details
console.error('Unexpected error:', err);
reportToSentry(err, { url: req.url, method: req.method });
}
res.status(statusCode).json({
error: {
message: isOperational ? err.message : 'Internal server error',
code: err.code || 'INTERNAL_ERROR',
},
});
});자주 묻는 질문
모든 오류를 잡아야 하나요?
아니요. 현재 수준에서 의미 있게 처리할 수 있는 오류만 잡으세요.
throw와 throw new Error()의 차이점은?
비Error 객체를 throw하면 스택 추적이 손실됩니다. 항상 new Error()를 사용하세요.
Promise.all()에서 오류를 처리하는 방법은?
Promise.all()은 어떤 promise가 거부되면 즉시 거부됩니다. 모든 결과를 얻으려면 Promise.allSettled()를 사용하세요.
오류 메시지에 무엇을 포함해야 하나요?
오류 메시지는 구체적이어야 합니다. 민감한 데이터는 절대 포함하지 마세요.