...
예외 (exception)
프로그래밍에서 예외(exception)란 실행 중인 프로그램에서 예기치 못한 상황이 발생하여 더 이상 진행할 수 없는 상황을 말한다. 예를 들어 문법적인 오류이거나 파일을 찾을 수 없거나, 메모리 부족 등의 상황에서 예외가 발생할 수 있다. 이러한 예외 상황은 프로그램의 실행을 중단시키거나, 비정상적인 결과를 초래할 수 있으므로, 프로그래머는 이러한 예외를 발생하지 않도록 미리 방지하는 것도 중요하지만, 발생한 예외를 처리할 수 있는 방법을 마련해야 한다.
예외 처리 (exception handling)
이를 위해 대부분의 프로그래밍 언어에서는 try-catch 구문을 제공하여 예외를 처리할 수 있도록 한다. 자바스크립트 역시 프로그램이 실행되는 도중 발생하는 예외를 처리하기 위해 try / catch 문을 사용한다.
try {
lalala; // 에러, 변수가 정의되지 않음!
} catch(err) {
alert(err.name); // ReferenceError
alert(err.message); // lalala is not defined
alert(err.stack); // ReferenceError: lalala is not defined at ... (호출 스택)
// 에러 전체를 보여줄 수도 있습니다. 이때, 에러 객체는 "name: message" 형태의 문자열로 변환됩니다.
alert(err); // ReferenceError: lalala is not defined
} finally {
// 무조건 실행되는 코드
}
try - catch - finally 문
try 문
- 에러가 났을 때 원상복구를 시도할 코드.
- 에러 발생시 코드의 실행 흐름이 catch 블록으로 옮겨간다.
try {
// 실행할 코드를 입력한다.
// 정상이라면 문제없이 실행된다. 하지만 경우에 따라 예외가 발생할 수 있다.
const result = someFunction();
} catch (e) {
}
catch 문
- 에러에 대한 정보를 담고 있는 객체(e 매개변수)를 사용할 수 있다.
e.name: 에러 이름e.message: 에러 상세 내용을 담고 있는 문자 메시지e.stack: 현재 호출 스택. 에러를 유발한 중첩 호출들의 순서 정보를 가진 문자열로 디버깅 목적으로 사용된다.
try {
// 예외가 발생할 가능성이 있는 코드
const result = someFunction();
} catch (e) {
// 이 블록 내부의 문장들은 오직 try 블록에서 예외가 발생할 경우에만 실행된다.
// 이 문장들에선 지역 변수 e를 사용하여 Error 객체 또는 앞에서 던진 다른 값을 참조할 수 있다.
// 이 블록에서는 어떻게든 그 예외를 처리할 수도 있고, 그냥 아무것도 하지 않고 예외를 무시할 수도 있다
}
try - catch 블록안의 변수는 함수나 for문 같이 지역 변수로 취급된다. 그냥 중괄호를 쓰는 문법 안의 변수는 대부분 지역 변수로 취급된다고 이해하면 된다.
finally 문
- try블록 안에서의 에러 발생 여부와 관계 없이 무조건 실행되어야 하는 코드
- return, break, continue등으로 코드의 실행 흐름이 즉시 이동되더라도 무조건 실행된다.
- 보통 catch와 같이 쓰인다.
- 에러가 안 났을 때: try - finally
- 에러가 났을 때: try - 에러발생 - catch - finally
try {
// 예외가 발생할 가능성이 있는 코드
const result = someFunction();
console.log(result);
} catch (error) {
// 예외가 발생했을 때 실행되는 코드
console.error(error);
} finally {
// 예외 발생 여부와 상관 없이 실행되는 코드
console.log('finally block executed');
}
finally는 반드시 써야될까?
catch 블록과 finally 블록은 선택적인 옵션으로 반드시 사용할 필요는 없다. 그럼 finally 문은 거추장 스러운데 굳이 사용하지 않아도 되지 않을까?
어떠한 로직에 대해 에러의 유무와 상관없이, 작업 후 초기화를 반드시 해야된다고 가정하자. 그래서 다음과 같이 try - catch 문 뒤에 작업 내역 삭제 로직을 실행하도록 구성하였다.
try {
작업
} catch (e) {
에러 핸들링
}
작업 내역 삭제
하지만 위 코드는 잘못 되었다. try문안에 작업이 에러가 뜨면 catch에서 핸들링을 하고 스크립트가 종료되게 되기 때문이다. 따라서 finally 예외처리를 명시함으로서 스크립트 종료되기전에 반드시 삭제가 실행되도록 지정해주어야 한다.
try {
작업
} catch (e) {
에러 핸들링
} finally {
작업 내역 삭제
}
finally 절은 try - catch 절을 빠져나가는 어떤 경우에도 실행된다. 심지어 return을 사용해 명시적으로 빠져나가려는 경우도 마찬가지이다.
예외 던지기 (throw)
JavaScript에서는 개발자가 직접 예외를 발생시킬 수 있다. 예외를 발생시키다 라는 것은 에러나 예외 상황을 알린다는 뜻이고, 예외를 강제로 발생시켜야 할 경우가 생길 때는 throw를 사용한다. 이를 '예외 던지기'라고 한다. 개발자가 예외를 던지면, 예외 객체가 생성되고, 이 객체는 프로그램 실행 중에 catch 블록에서 처리되게 된다.
아래 코드에서는 브라우저 프롬프트 창을 이용해 사용자로부터 숫자를 입력 받고, 만일 짝수가 아닐 경우 강제적으로 에러를 발생시켜 던진다. 이렇게 throw 된 예외 객체는 catch 블록에서 처리된다.
try {
const even = parseInt(prompt('짝수를 입력하세요'));
if (even % 2 !== 0) {
throw new Error('짝수가 아닙니다.');
}
} catch (e) {
alert(e.message);
}
// 3을 입력할 경우
// Error: 짝수가 아닙니다.
이처럼 예외를 던질 때는 new Error('에러 메세지') 와 같이 예외 객체를 생성하여 던진다. 그리고 예외 객체를 생성할 때는, 보통 예외에 대한 설명을 포함한 메시지를 함께 전달하는 편이다.
예외 던지기는 예외 처리를 위해 중요한 기능 중 하나이다. 개발자는 예외를 던져서 예외 상황을 감지하고, 이를 적절하게 처리할 수 있도록 한다.
다중 예외 처리하기
자바와 자바스크립트의 예외 처리 차이
자바와 자바스크립트의 예외처리는 몇 가지 차이점이 있다.
자바는 컴파일 타임에 예외를 검사하고, checked exception과 unchecked exception을 구분한다. checked exception은 반드시 try/catch로 처리해야 하고, unchecked exception은 런타임에 발생하는 오류로 처리하지 않아도 된다. checked exception으로는 IllegalArgumentException, IndexOutOfBoundsException, NullPointerException 등이 있다.
반면 자바스크립트는 인터프리터 언어이므로 컴파일 타임에 예외를 검사하지 않고, 모든 예외는 런타임에 발생하며 Error 객체를 생성하게 된다. 그래서 자바스크립트에는 checked exception이 없으며 대신에 TypeError, ReferenceError, SyntaxError, RangeError 등이 존재한다.
자바의 다중 예외 처리
자바(Java)에서는 여러가지 예외(Exception)를 구분하여 처리하고 싶을 때 catch 블럭을 여러개 나열하거나, 파이프 기호 | 를 통해 여러개의 예외 타입을 처리할 수 있다.
try {
// ...
} catch (IllegalArgumentException | IndexOutOfBoundsException error) {
System.err.println("Caught an error: " + error.getMessage());
} catch (NullPointerException error) {
System.err.println("Caught a NullPointerException: " + error.getMessage());
} catch (Exception error) {
System.err.println("Caught an unexpected error: " + error);
}
자바스크립트의 다중 예외 처리
하지만 자바스크립트에서는 예외 타입이 명시적으로 되어 있지 않기 때문에 instanceof 연산자를 사용하여 조건 분기문을 통해 예외 객체의 타입을 확인하고 구분하여 처리하여야 한다.
try {
// ...
} catch (error) {
if (error instanceof TypeError) {
console.error("Caught a TypeError: " + error.message);
} else if (error instanceof ReferenceError) {
console.error("Caught a ReferenceError: " + error.message);
} else if (error instanceof SyntaxError) {
console.error("Caught a SyntaxError: " + error.message);
} else if (error instanceof RangeError) {
console.error("Caught a RangeError: " + error.message);
} else {
console.error("Caught an unexpected error: " + error);
}
}
혹은 error.name 프로퍼티를 통해서도 다중 예외 처리가 가능하다.
try {
// 에러가 발생할 수 있는 코드
} catch (error) {
// 에러 객체를 받아서 처리
if (error.name === "ReferenceError") {
// 참조 에러일 경우의 처리
} else if (error.name === "SyntaxError") {
// 구문 에러일 경우의 처리
} else if (error.name === "TypeError") {
// 타입 에러일 경우의 처리
} else {
// 그 외의 에러일 경우의 처리
}
}
이렇게 하면 자바의 다중 catch 문을 자바스크립트의 단일 catch 문으로 표현할 수 있다.
사용자 정의 예외 만들기
만일 해당 로직의 예외에 대해 자바스크립트가 제공하는 기본 예외 타입중 적절한 예외 타입이 없다면, 프로그래머가 직접 커스텀 예외를 만들어 사용할 수 가 있다. 자바스크립트에서 커스텀 예외를 만들려면 Error 클래스를 상속받아 새로운 클래스를 만들어야 한다. 아래 예시 코드는 MyError 라는 커스텀 예외 클래스를 만드는 코드이다.
// 사용자 정의 예외 타입
class MyError extends Error { // Error 부모 클래스를 상속한다.
constructor(message) {
super(message);
this.name = 'MyError';
}
}
try {
throw new MyError('Something went wrong');
} catch (error) {
// 에러 객체를 받아서 처리
if (error.name === "MyError") {
console.log(error.message); // 'Something went wrong'
}
}
이렇게 자바스크립트가 제공하는 Error 상위 클래스를 상속받아 커스텀 예외 클래스를 만들고 throw 키워드를 사용하여 커스텀 예외 객체를 생성하고 던지면 catch 블록에서 커스텀 예외 처리를 다룰 수 있게 된다.
예외 처리 Stack 추적
자바스크립트 예외 객체의 프로퍼티에는 예외 이름(name)이나 예외 메세지(message) 외에 한가지 프로퍼티가 더 있는데 바로 스택(stack) 이다.
try {
lalala; // 에러, 변수가 정의되지 않음!
} catch(err) {
alert(err.name); // ReferenceError
alert(err.message); // lalala is not defined
alert(err.stack); // ReferenceError: lalala is not defined at ... (호출 스택)
}
이 스택 프로퍼티를 이용해 서로 함수를 호출하여 얽히고 섥힌 호출 코드 중에 어느 함수 호출에서 에러가 발생했는지 추적할 수가 있다.
예를 들어 아래와 같이 함수들끼리 서로 호출한다고 해보자.
function a() {
console.log('a: calling b');
b();
console.log('a: done');
}
function b() {
console.log('b: calling c');
c();
console.log('b: done');
}
function c() {
console.log('c: throwing error');
throw new Error('c error');
console.log('c: done');
}
try {
a();
} catch (err) {
console.log(err.stack);
}
함수 a에서 함수 b를 호출하고 함수 b에서는 함수 c를 호출한다면, 함수 c가 실행을 마칠 때 실행 흐름은 함수 b로 돌아가고 b가 실행을 마칠 때 실행 흐름은 함수 a로 돌아간다. 그렇기 때문에 c가 실행 중일 때는 a와 b는 완료될 수 없고 이렇게 완료되지 않은 함수가 쌓이는 것을 Call Stack 이라 부른다.
자바스크립트 인터프리터는 함수의 호출 과정을 모두 추적하고 있기 때문에, 발생한 에러는 콜 스택 어디에서든 캐치할 수 있다. 에러를 캐치하면 콜 스택에서 문제 해결에 유용한 정보를 얻을 수 있다. 만약 함수 c에서 에러가 일어났다면, 콜 스택은 c에서 일어난 에러를 보고하는 데 그치지 않고 b가 c를 호출했으며, b는 a에서 호출했다는 것도 함께 알려주기 때문에 디버그에 유용하게 사용된다.
비동기 함수에서의 예외 처리
setTimeout
try - catch는 기본적으로 동기적으로 동작한다. 그래서 setTimeout처럼 ‘스케줄 된(scheduled)’ 코드에서 발생한 예외는 try..catch에서 잡아낼 수 없다.
try {
setTimeout(function() {
noSuchVariable; // 스크립트는 여기서 죽습니다.
}, 1000);
} catch (e) { // 스크립트는 이미 멈췄기 때문에, 비동기 환경에서 뭐가 됬든 작동 안함.
alert( "작동 멈춤" );
}
setTimeout에 넘겨진 익명 함수는 엔진이 try..catch를 떠난 다음에서야 실행되기 때문이다. 따라서 스케줄 된 함수 내부의 예외를 잡으려면, try..catch를 반드시 함수 내부에 구현해야 한다.
setTimeout(function() {
try {
noSuchVariable; // 이제 try..catch에서 에러를 핸들링 할 수 있습니다!
} catch {
alert( "에러를 잡았습니다!" );
}
}, 1000);
Promise 객체
Promise 객체는 세 가지 상태를 가질 수 있다.
- pending - Promise 객체에 결과값이 채워지지 않은 상태
- fulfilled - Promise 객체에 결과값이 채워진 상태(이때 then메소드 또는 await를 통해 무언가를 실행했다.)
- rejected - Promise 객체에 결과값을 채우려고 시도하다가 에러가 난 상태
then 핸들러 메소드에 첫 번째 인수로 넘겨준 콜백이 실행되지 않고, 두 번째 인수로 넘겨준 콜백이 실행된다. 그리고 이 콜백에는 에러 객체가 첫번째 인수로 주어진다.
const p = new Promise(resolve => {
const even = parseInt(prompt('짝수를 입력하세요'));
if (even % 2 !== 0) {
throw new Error('짝수가 아닙니다.');
} else {
// 짝수면 fullfiled되어 then메소드의 첫번째 인수로 들어간 함수가 실행
resolve(even);
}
});
// then 콜백에서 반환된 값이 다음Promise의 값이 된다.
p
.then(even => {
return '짝수입니다.'; // resolve() 됬을 경우
})
.catch(e => {
return e.message; // throw 됬을 경우
})
.then(alert); // 앞서 리턴값을 alert의 인자값으로 대입
Promise가 rejected 상태가 되었을 때 catch 메소드를 통해 다음과 같은 방법으로도 에러 처리 콜백을 지정해 줄 수 있다.
# 참고자료
https://tcpschool.com/javascript/js_exception_exception
https://chiabi.github.io/2018/06/11/try-catch/
https://velog.io/@smooth97/-Learning-Javascript-%EC%98%88%EC%99%B8%EC%99%80-%EC%97%90%EB%9F%AC-%EC%B2%98%EB%A6%AC
https://gangzzang.tistory.com/entry/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8JavaScript-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%ACException-handling
https://ktko.tistory.com/entry/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.