에러 처리의 필요성
프로그래밍에서 에러가 발생하지 않는 코드를 작성하는 것은 사실상 불가능하다. 에러는 프로그램의 실행 중 언제든 발생할 수 있으며, 이러한 에러를 적절히 처리하지 않으면 프로그램은 강제로 종료된다.
console.log('[Start]');
foo(); // ReferenceError: foo is not defined
// 에러 발생으로 인해 프로그램이 강제 종료된다
console.log('[End]'); // 실행되지 않는다
이러한 문제를 해결하기 위해 try...catch 문을 사용하면 발생한 에러에 대해 적절하게 대응할 수 있다. 이를 통해 프로그램이 강제 종료되는 것을 방지하고, 계속해서 코드를 실행시킬 수 있다.
console.log('[Start]');
try {
foo();
} catch (error) {
console.log('[에러 발생]', error);
// [에러 발생] ReferenceError: foo is not defined
}
// 에러가 발생해도 프로그램이 종료되지 않고 실행을 계속한다
console.log('[End]');
에러에는 직접적인 에러와 간접적인 에러가 있다. 직접적인 에러는 명시적으로 에러가 발생하는 경우이고, 간접적인 에러는 예외적인 상황으로 인해 발생할 수 있는 잠재적 에러를 말한다. 예를 들어, DOM 조작 시 발생할 수 있는 에러를 살펴보자.
// DOM에 button 요소가 존재하지 않는 경우
const $button = document.querySelector('button'); // null 반환
// 이 시점에서는 에러가 발생하지 않았지만,
$button.classList.add('disabled');
// 이 시점에서 TypeError가 발생한다
// TypeError: Cannot read property 'classList' of null
이러한 간접적인 에러는 if 문을 사용한 조건 확인이나 옵셔널 체이닝 연산자를 통해 방지할 수 있다.
// 옵셔널 체이닝을 사용한 안전한 접근
const $button = document.querySelector('button');
$button?.classList.add('disabled'); // 에러 없이 안전하게 처리된다
따라서 우리가 작성하는 코드에서는 항상 예외적인 상황이 발생할 수 있다는 것을 전제하고, 이에 대응하는 코드를 작성하는 것이 매우 중요하다. 이는 프로그램의 안정성과 신뢰성을 높이는 핵심적인 방법이다.
try...catch...finally 문
에러 처리를 구현하는 방법은 크게 두 가지로 나눌 수 있다.
- 첫 번째: 값 검사를 통한 에러 방지 방법
- 두 번째: try...catch...finally로 에러 처리 코드를 미리 등록해두는 방법
첫 번째 방법인 값 검사는 조건문이나 단축 평가, 옵셔널 체이닝 연산자 등을 사용하여 에러가 발생할 수 있는 상황을 미리 점검하는 것이다.
두 번째 방법인 try...catch...finally 문은 에러 처리 코드를 미리 등록해두고, 에러가 발생했을 때 실행할 코드를 지정해두는 방식이다.
try...catch...finally 문의 기본 구조는 다음과 같다.
try {
// 실행할 코드 (에러가 발생할 가능성이 있는 코드)
} catch (err) {
// try 블록에서 에러가 발생하면 이 블록의 코드가 실행된다
// err 변수에는 발생한 Error 객체가 전달된다
} finally {
// 에러 발생 여부와 상관없이 반드시 한 번 실행되는 코드
}
실행 순서는 다음과 같다:
- try 블록의 코드가 실행된다.
- 에러가 발생하지 않으면 try 블록의 코드가 모두 실행된다.
- 에러가 발생하면 즉시 catch 블록으로 이동하여 catch 블록의 코드가 실행된다.
- finally 블록은 에러 발생 여부와 관계없이 반드시 한 번 실행된다.
그리고 finally 블록은 필요하지 않다면 생략할 수 있다.
console.log('[Start]');
try {
// 존재하지 않는 함수 실행
foo();
} catch (err) {
// 에러 발생 시 실행되는 코드
console.error(err); // ReferenceError: foo is not defined
} finally {
// 항상 실행되는 코드
console.log('finally 블록 실행');
}
console.log('[End]');
catch 블록도 생략할 수 있지만, catch 블록이 없는 try 문은 에러를 처리할 수 없으므로 의미가 없다. 따라서 일반적으로 catch 블록은 생략하지 않는다.
Error 객체
Error 객체는 에러에 대한 정보를 담고 있는 객체로, Error 생성자 함수를 통해 생성할 수 있다. Error 객체는 에러의 종류를 나타내는 여러 하위 객체들의 부모 객체이다.
// Error 객체 생성
const error = new Error('에러 메시지');
Error 생성자 함수가 생성한 에러 객체는 두 가지 중요한 프로퍼티를 가진다.
- message: Error 생성자 함수에 인수로 전달한 에러 메시지
- stack: 에러가 발생한 콜스택의 호출 정보 (디버깅용)
자바스크립트는 다음과 같은 7가지의 에러 생성자 함수를 제공한다.
1. Error: 일반적인 에러 객체
2. SyntaxError
1 @ 1; // SyntaxError: Invalid or unexpected token
문법적 오류가 있을 때 발생하는 에러
3. ReferenceError
foo(); // ReferenceError: foo is not defined
존재하지 않는 변수를 참조할 때 발생하는 에러
4. TypeError
null.foo; // TypeError: Cannot read property 'foo' of null
타입이 유효하지 않을 때 발생하는 에러
5. RangeError
new Array(-1); // RangeError: Invalid array length
숫자값이 허용 범위를 벗어났을 때 발생하는 에러
6. URIError
decodeURIComponent('%'); // URIError: URI malformed
URI 관련 함수의 사용이 잘못되었을 때 발생하는 에러
7. EvalError: eval 함수에서 발생하는 에러 (현재는 거의 사용되지 않음)
throw 문
Error 생성자 함수로 에러 객체를 생성하는 것만으로는 에러가 발생하지 않는다. 에러 객체의 생성과 에러의 발생은 의미가 완전히 다른 것이다. 에러를 실제로 발생시키려면 try 코드 블록 내에서 throw 문으로 에러 객체를 던져야 한다.
// 에러 객체만 생성한 경우 - 에러 발생하지 않음
try {
new Error('something wrong');
console.log('이 코드는 실행된다');
} catch (error) {
console.log(error);
}
// throw로 에러를 발생시킨 경우
try {
throw new Error('something wrong');
console.log('이 코드는 실행되지 않는다');
} catch (error) {
console.log(error); // Error: something wrong
}
throw 문의 표현식에는 어떤 값이라도 사용할 수 있지만, 일반적으로는 Error 객체를 사용하는 것이 가장 좋다. Error 객체는 에러의 종류와 원인을 정확하게 파악할 수 있게 해주기 때문이다.
// 콜백 함수의 타입 체크를 하는 repeat 함수
const repeat = (n, f) => {
// 매개변수 f가 함수가 아니면 TypeError를 발생시킨다
if (typeof f !== 'function') {
throw new TypeError('f must be a function');
}
for (let i = 0; i < n; i++) {
f(i);
}
};
try {
repeat(2, 1); // 두 번째 인수가 함수가 아님
} catch (err) {
console.error(err); // TypeError: f must be a function
}
실제 개발에서의 활용 예시는 위와 같다.
에러의 전파
에러는 호출자(caller) 방향으로 전파된다. 즉, 콜 스택의 아래 방향으로 전파되는데, 이는 실행 중인 실행 컨텍스트가 푸시되기 직전에 푸시된 실행 컨텍스트 방향을 의미한다.
const foo = () => {
throw new Error('foo에서 발생한 에러'); // ④
};
const bar = () => {
foo(); // ③
};
const baz = () => {
bar(); // ②
};
try {
baz(); // ①
} catch (err) {
console.error(err);
}
위 코드의 실행 순서와 에러 전파 과정은 다음과 같다.
- ①에서 baz 함수가 호출된다.
- baz 함수 내부에서 ②의 bar 함수가 호출된다.
- bar 함수 내부에서 ③의 foo 함수가 호출된다.
- foo 함수 내부에서 ④에서 에러가 throw된다.
- 이때 발생한 에러는 호출자의 방향으로 전파된다: foo → bar → baz → 전역
특별히 주의해야 할 점은 비동기 함수에서의 에러 처리이다. setTimeout이나 프로미스의 후속 처리 메서드의 콜백 함수에서 발생한 에러는 호출자가 없다. 이는 이러한 함수들이 태스크 큐나 마이크로태스크 큐에 일시 저장되었다가, 콜 스택이 비면 이벤트 루프에 의해 콜 스택으로 푸시되어 실행되기 때문이다.
// 비동기 함수의 에러 처리
setTimeout(() => {
throw new Error('비동기 처리 중 발생한 에러');
}, 1000);
// 이 에러는 캐치되지 않는다
try {
setTimeout(() => {
throw new Error('에러');
}, 1000);
} catch (err) {
// 이 코드는 실행되지 않는다
console.error(err);
}
이러한 비동기 처리에서의 에러는 비동기 함수 내부에서 try...catch로 처리해야 하며, 프로미스를 사용하는 경우에는 프로미스의 에러 처리 메서드인 catch를 사용하는 것이 좋다.
요약
에러 처리의 기본 개념
에러의 특성과 중요성
- 프로그래밍에서 에러가 발생하지 않는 코드를 작성하는 것은 불가능하다.
- 에러는 프로그램 실행 중 언제든지 발생할 수 있는 것이 당연하다.
- 적절한 에러 처리가 없으면 프로그램이 강제 종료되는 심각한 문제가 발생한다.
에러의 종류
- 직접적인 에러
- 명시적으로 발생하는 에러 (예: ReferenceError, TypeError 등)
- 코드 실행 즉시 발견되는 에러
- 간접적인 에러 (예외적 상황)
- 잠재적으로 발생할 수 있는 에러
- 특정 조건에서만 발생하는 에러
- DOM 조작 시의 null 참조 등이 대표적
에러 처리 방법의 상세 분석
1. 값 검사를 통한 에러 방지
// 옵셔널 체이닝 예시
const element = document.querySelector('#someId');
element?.addEventListener('click', handleClick);
- if문을 통한 조건 확인
- 단축 평가 사용
- 옵셔널 체이닝 연산자 활용
try {
// 에러가 발생할 수 있는 코드
riskyOperation();
} catch (err) {
// 에러 발생 시 실행될 코드
console.error(err);
} finally {
// 항상 실행되는 코드
cleanup();
}
Error 객체의 상세 이해
1. Error 객체의 기본 구조
- message 프로퍼티: 에러 메시지 포함
- stack 프로퍼티: 에러 발생 위치의 호출 스택 정보
2. 주요 Error 타입들
// 1. SyntaxError - 문법적 오류
1 @ 1; // Invalid token
// 2. ReferenceError - 참조 오류
console.log(undefinedVariable);
// 3. TypeError - 타입 오류
null.toString();
// 4. RangeError - 범위 오류
new Array(-1);
// 5. URIError - URI 관련 오류
decodeURIComponent('%');
throw 문과 에러 발생
1. throw 문의 기본 개념
- 에러 객체 생성과 에러 발생은 별개의 개념
- throw를 통해 명시적으로 에러를 발생시킴
2. 실제 활용 예시
const validateUser = user => {
if (!user.name) {
throw new Error('사용자 이름이 필요합니다');
}
if (!user.age || user.age < 0) {
throw new RangeError('올바른 나이를 입력하세요');
}
// 검증 통과
return true;
};
에러의 전파 메커니즘
1. 기본 전파 규칙
- 에러는 호출자(caller) 방향으로 전파
- 콜 스택의 아래 방향으로 진행
- 전파 과정에서 catch되지 않으면 프로그램 종료
2. 비동기 상황의 에러 처리
// 프로미스의 에러 처리
new Promise((resolve, reject) => {
throw new Error('프로미스 에러');
}).catch(err => {
console.error('에러 처리:', err);
});
// async/await의 에러 처리
async function handleAsync() {
try {
await riskyAsyncOperation();
} catch (err) {
console.error('비동기 에러:', err);
}
}
에러 처리의 모범 사례
1. 체계적인 에러 처리 전략
- 예상 가능한 에러에 대한 방어적 프로그래밍
- 명확한 에러 메시지와 타입 사용
- 적절한 로깅과 모니터링 구현
2. 커스텀 에러 클래스 활용
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
class DatabaseError extends Error {
constructor(message) {
super(message);
this.name = 'DatabaseError';
}
}
최적화된 에러 처리 방안
1. 성능 고려사항
- try...catch 블록은 성능에 영향을 줄 수 있음
- 필요한 곳에만 선별적으로 사용
- 에러 객체 생성 비용 고려
2. 실무적 접근
- 로깅과 모니터링 시스템 연동
- 사용자 경험을 고려한 에러 처리
- 개발 환경과 프로덕션 환경의 에러 처리 전략 차별화
예상문제 [🔥]
https://github.com/junh0328/prepare_frontend_interview?tab=readme-ov-file
에러 처리를 왜 해야 하나요? 🔥
에러 처리가 필요한 이유는 크게 세 가지로 설명할 수 있습니다.
첫째, 프로그램의 안정성을 위해서입니다. 에러가 발생하지 않는 코드를 작성하는 것은 사실상 불가능합니다. 만약 발생한 에러를 적절히 처리하지 않으면 프로그램이 예기치 않게 종료되어 사용자 경험이 크게 저하될 수 있습니다.
둘째, 예외적인 상황에 대한 대응을 위해서입니다. 예를 들어 DOM 조작 시 특정 요소가 존재하지 않거나, 서버 통신 중 네트워크 오류가 발생하는 등의 상황이 언제든 발생할 수 있습니다. 이러한 상황들을 미리 예측하고 대응하는 코드를 작성해야 안정적인 프로그램을 만들 수 있습니다.
셋째, 디버깅과 유지보수를 위해서입니다. 적절한 에러 처리는 문제가 발생했을 때 그 원인을 파악하고 해결하는 데 큰 도움이 됩니다. 에러 메시지와 스택 트레이스를 통해 문제의 발생 지점과 원인을 정확히 파악할 수 있습니다.
요약
에러가 발생하지 않는 코드를 작성하는 것은 불가능하기 때문에, 프로그램의 안정성을 위해 에러 처리가 필수적입니다. 에러를 적절히 처리하지 않으면 프로그램이 예기치 않게 종료될 수 있고, 예외적인 상황에 대응하기 위해서도 에러 처리가 필요합니다. 또한 디버깅과 유지보수를 위해서도 중요합니다.
자바스크립트에서 에러를 처리하는 방법에는 뭐가 있을까요?
자바스크립트에서 에러를 처리하는 방법은 크게 세 가지가 있습니다.
첫째, try...catch...finally 문을 사용하는 방법입니다. 이는 가장 기본적인 에러 처리 방식입니다.
try {
// 에러가 발생할 수 있는 코드
riskyOperation();
} catch (err) {
// 에러 발생 시 실행될 코드
console.error('에러 발생:', err);
} finally {
// 에러 발생 여부와 관계없이 항상 실행되는 코드
cleanup();
}
둘째, Error 객체를 사용하는 방법입니다. 자바스크립트는 다양한 내장 에러 객체를 제공합니다.
// 기본 Error 객체
const error = new Error('에러 메시지');
// 다양한 에러 타입
new SyntaxError('문법 에러');
new TypeError('타입 에러');
new ReferenceError('참조 에러');
셋째, throw 문을 사용하여 직접 에러를 발생시키는 방법입니다.
function divide(a, b) {
if (b === 0) {
throw new Error('0으로 나눌 수 없습니다');
}
return a / b;
}
이러한 방법들은 상황에 따라 적절히 조합하여 사용됩니다. 예를 들어, throw로 발생시킨 에러를 try...catch로 잡아내는 식입니다. 특히 비동기 작업이나 외부 API 호출 같은 실패 가능성이 있는 작업에서는 이러한 에러 처리가 필수적입니다.
요약
자바스크립트에서는 try...catch...finally 문으로 에러를 처리하고, Error 객체를 통해 에러 정보를 담으며, throw 문으로 에러를 발생시킬 수 있습니다. 이 세 가지 방법은 서로 연관되어 사용되는데, 예를 들어 throw로 Error 객체를 던지고 이를 try...catch로 잡아내는 방식으로 활용됩니다.
'🧱 프론트엔드 주제 > JavaScript' 카테고리의 다른 글
[모던 자바스크립트 Deep Dive] 49장 - Babel과 Webpack을 이용한 ES6+/ES.NEXT 개발 환경 구축 (1) | 2024.12.08 |
---|---|
[모던 자바스크립트 Deep Dive] 48장 - 모듈 (0) | 2024.11.30 |
[모던 자바스크립트 Deep Dive] 46장 - 제너레이터와 async/await (1) | 2024.11.30 |
[모던 자바스크립트 Deep Dive] 45장 - Promise (0) | 2024.11.26 |
[모던 자바스크립트 Deep Dive] 44장 - REST API (2) | 2024.11.24 |