Section 11. 자바스크립트 에러 핸들링
에러 핸들링 error handling 의 필요성
에러 발생에 대비하지 않으면 프로그램이 종료된다.
console.log('에러 발생 전');
// ⚠️ 오류를 발생시키는 코드
(3).split('');
// 출력되지 않음
console.log('에러 발생 후');
I. 자바스크립트의 에러 핸들링
1. try ...catch 문
console.log('에러 발생 전');
try {
(3).split('');
} catch (e) {
console.error('🛑 에러!!', e);
}
console.log('에러 발생 후');
try ...catch문을 썼더니 맨 위에 있는 예시에서는 프로그램이 멈춰서 마지막 출력문이 출력 되지 않았지만 이번에는 출력이 되었다.
정리하자면
try 블록
- 에러 발생 여지가 있는 코드를 포함한다.
- 이곳에서 발생한 에러는 프로그램을 멈추지 않는다.
catch 블록
- 에러 발생시 실행할 코드를 포함한다.
- 발생한 오류 객체를 인자로 받는다.
const arr = ['ABC', '가나다', 123, '123'];
function getLetterArray (str) {
// 💡 인자로 어떤 타입의 값이 주어질지 모르는 상황
try {
return str.split('');
} catch (e) {
console.error('🛑 에러!!', e);
return [];
}
}
arr.forEach(i => {
console.log(getLetterArray(i));
});
인자로 어떤 값을 받을지 모르는 상황에서 try블록 에서는 일단 작업을 한다. 그리고 아무 에러도 없으면 함수를 종료한다.
try블록에서 에러가 발생하게 되면 catch 블록으로 넘어가서 작업을 하고 반환값으로는 빈 배열을 반환하고 함수를 종료하게 된다.
2. try ... catch ... finally 문
finally 블록
오류가 발생 여부와 관계없이 한 번 실행되는 코드를 포함한다.
try나 catch문에 return이 있더라도 반드시 실행된다.
function connect () { console.log('☀️', '통신 연결'); }
function disconnect () { console.log('🌙', '통신 연결 해제'); }
function sendArray (arr) { console.log('전송', arr); }
function sendStringAsArray (str) {
connect();
try {
sendArray(str.split(''));
return true;
} catch (e) {
console.error('🛑 에러!!', e);
return false;
} finally {
// 💡 전송 성공 여부와 관계없이 연결은 끊어야 함
disconnect();
console.log('- - - - - - - -');
}
// ❓ 이곳에 넣는 것과 무엇이 다른가?
// 아래로 대체하여 실행해 볼 것
// disconnect();
// console.log('- - - - - - - -');
}
['ABC', '가나다', 123, '123'].forEach(i => {
console.log(
sendStringAsArray(i)
? '[성공]' : '[실패]', '\n\n'
);
});
오류발생 여부와 관게없이 finally 블록에 있는 작업은 실행이 됐다.
그렇다면 finally 블록에서의 작업을 밖으로 빼서 실행하면 어떨까?
함수는 return문을 만나면 종료된다. 그리고 disconnect가 실행이 되지 않아 출력이 안되는 것을 볼 수 있다. 실무에서 connect 등을 자원을 썼으면 반드시 종료를 해 주어야 하는데 이처럼 finally에서 disconnect를 하지 않으면 자원이 부족해져서 서버의 기능이 마비될 수 있으니 조심해야 한다.
II. Error 객체
- 에러 발생 시 던져지는 thrown 객체이다.
- 에러에 대한 정보를 담고 있다.
- 에러가 발생하지 않아도, 직접 생성하여 던지기가 가능하다.
1. 기본 생성과 사용법
const error = new Error('철권 8 발매일');
console.error(error);
두 번째 인자로 이유를 명시할 수도 있다.
const error = new Error(
'철권 8 발매일',
{ cause: '보이지가 않음' }
);
console.error(error);
이것을 확인해보면 다음과 같다
console.log(error.name); //Error
console.log(error.message); //철권 8 발매일
// cause를 입력했을 경우
console.log(error.cause); //보이지가 않음
이처럼 에러 객체의 인스턴스 프로퍼티와 메서드를 통해서 내용을 확인 할 수도 있다.
의도적으로 에러 발생시키는 법
throw new Error('이유를 묻지 마라');
2. 에러의 여러 종류
아래의 에러들은 모두 Error부터 상속받는다.
어떤 문제에 의한 에러인지 쉽게 식별 가능하도록 하는 것들이다.
오류에 종류에 따라 대처하는 예시
const errorFuncs = [
// 자료형에 맞지 않는 메서드 호출
() => { (3).split(''); },
// 선언되지 않은 함수 호출
() => { hello(); },
// 부적절한 숫자를 인자로 전달
() => { (123.45).toFixed(-1); }
];
errorFuncs.forEach(func => {
try {
func();
} catch (e) {
if (e instanceof TypeError) {
console.error('자료형 확인하세요.');
return;
}
if (e instanceof ReferenceError) {
console.error('선언 안 된 거 쓴 거 없는지 확인하세요.');
return;
}
console.error('아니, 뭘 한 거에요?');
}
});
여러 종류의 에러를 발생시키는 함수들을 각각 만들고 catch 문을 활용하여 오류의 종류에 따라 대처하게 만들었다.
직접 오류를 생성하여 던지기
// 특정 월의 당번으로 지원하는 함수
function applyForMonth (date) {
try {
if (typeof date !== 'number') {
throw new TypeError('숫자로 입력해주세요.');
}
if (date < 1 || date > 12) {
throw new RangeError('유효한 월을 입력해주세요.');
}
console.log(`${date}월 당번으로 등록되셨습니다.`);
} catch (e) {
console.error('🛑 에러 발생!', e);
console.log('다시 등록해주세요.');
}
}
applyForMonth('5');
applyForMonth(13);
각각 에러가 발생한다. 컴퓨터는 13이 들어갔을때 이것이 오류라는 것을 인지하지 못하기 때문에 수동적으로 오류를 발생시킨 것이다.
III. 에러 버블링 error bubbling
다른 함수를 호출했을 때
- 에러 발생시 해당 함수에서 잡지 않으면 호출한 곳으로 던져진다.
- 다중 호출시 에러를 핸들링하는 코드가 있는 호출자까지 전달된다.
function func1 () {
throw new Error('에러');
}
function func2 () {
func1();
}
function func3 () {
func2();
}
function func4 () {
try {
func3();
} catch (e) {
console.error(e);
}
console.log('실행완료');
}
func4();
func1 에서 에러가 났는데 func1에서 에러를 처리하는 로직이 없어서 func1을 호출한 func2로 애러를 핸들링하는 코드가 전달되고 거기에도 없으니 func2를 호출한 func3로 이동하고 결국 func4까지 가서 에러가 처리된 것을 볼 수 있다.
이런 현상은 좋지 않으니 에러는 가능한 발생한 곳 가까이서 처리하는 것이 좋다.