우리는 프로그래밍을 하며 크고 작은 에러들을 만난다.
에러들을 만나면 슥 긁어서 구글링을 하며 해결을 하곤 하지만, 해결을 하는 것만큼 중요한 건 처음 코드를 구현할 때부터 에러가 날 상황을 예측하며 미리 설계해두는 것이다.
이를 프로그래밍에서는 '예외 처리 (exception handling)' 이라고 한다.
예외 처리를 잘 해두면 다음과 같은 장점이 있다:
❗ 프로그램의 갑작스런 종료를 방지할 수 있다.
→ 예상치 못한 오류로 인해 앱이 중단되는 것을 막고, 사용자 경험을 지킬 수 있다.
에러의 원인을 쉽게 파악할 수 있다.
→ 로그나 메시지로 에러 정보를 출력해두면, 디버깅이 훨씬 쉬워진다.
대체 흐름(Fallback)을 제공할 수 있다.
→ 예를 들어 네트워크 요청이 실패하면 로컬 데이터를 보여주는 등, 사용자에게 최소한의 경험을 보장할 수 있다.
보안적인 측면에서도 유리하다.
→ 민감한 정보가 노출되지 않도록 에러 상황을 통제할 수 있다.
모듈 간의 책임을 명확히 나눌 수 있다.
→ 어느 모듈에서 어떤 예외를 발생시키고, 누가 그것을 처리할지를 설계함으로써 코드 구조가 깔끔해진다.
자바스크립트 뿐만 아니라 많은 프로그래밍 언어에서 예외 처리 를 위해 try-catch (finally) 구문을 사용한다.
try {
// 오류가 발생할 가능성이 있는 코드
} catch (error) {
// 오류가 발생했을 때 실행되는 코드
} finally {
// (선택) 오류와 관계없이 항상 실행되는 코드
}
에러에 대한 정보를 담고 있는 객체(e 매개변수)를 사용할 수 있다.
e.stack은 에러가 발생했을 때의 호출 스택(Call Stack) 정보를 문자열로 담고 있는 속성
"이 에러가 어디서 어떻게 발생했는지를 추적할 수 있는 로그"를 담고 있다

에러 타입과 메시지: Error: 문제 발생!
어떤 함수에서, 어떤 파일의 몇 번째 줄에서 에러가 발생했는지
위에서 아래로 함수 호출 흐름이 기록됨 (가장 마지막에 호출된 함수가 맨 위)
a() 호출됨 → Stack: [a]
a() 내부에서 b() 호출 → Stack: [a, b]
b() 내부에서 c() 호출 → Stack: [a, b, c]
c()에서 throw new Error() → 💥 에러 발생
현재까지 호출된 함수들을 위에서 아래로 출력함
| 줄 | 의미 |
|---|---|
at c | 에러가 실제 발생한 위치 |
at b | c()를 호출한 함수 |
at a | b()를 호출한 함수 |
at <anonymous> | a()를 호출한 위치 (즉, 전역 실행 컨텍스트) |
여기서 anonymous는 "익명 함수" 또는 "이름 없는 실행 컨텍스트"를 뜻함
브라우저 콘솔은 내부적으로 전체 입력을 익명 함수에 감싸서 실행하기에
그래서 function a() {}처럼 이름을 줬어도 결국 로 출력됨.
에러가 안 났을 때: try -> finally
에러가 났을 때: try -> 에러발생 -> catch -> finally
finally는 왜, 어떻게 쓸 수 있을까?
어떠한 로직에 대해 에러의 유무와 상관없이, 작업 후 초기화를 반드시 해야된다고 가정하자. 그래서 다음과 같이 try - catch 문 뒤에 작업 내역 삭제 로직을 실행하도록 구성하였다.
try {
작업
} catch (e) {
에러 핸들링
}
작업 내역 삭제
try문안에 작업이 에러가 뜨면 catch에서 핸들링을 하고 스크립트가 종료되게 되기 때문이다.
따라서 finally 예외처리를 명시함으로서 스크립트 종료되기전에 반드시 삭제가 실행되도록 지정해주어야 한다.
try {
작업
} catch (e) {
에러 핸들링
} finally {
작업 내역 삭제
}
자바스크립트에서는 개발자가 임의로 에러를 발생시킬 수 있다.
개발자가 예외를 던지면, 예외 객체가 생성되고, 이 객체는 프로그램 실행 중에 catch 블록에서 처리되게 된다.
try {
const even = parseInt(prompt('짝수를 입력하세요'));
if (even % 2 !== 0) {
throw new Error('짝수가 아닙니다.');
}
} catch (e) {
alert(e.message);
}
// 3을 입력할 경우
// Error: 짝수가 아닙니다.
throw new Error('짝수가 아닙니다.') 에서
e.name = Error
e.message = 짝수가 아닙니다.
가 된다.
throw new Error 할때 이 'Error'에 들어가는 것이 e.name 인데 종류가 있다.
| 에러 이름 | 설명 | 발생 예시 |
|---|---|---|
Error | 모든 에러의 기본형. 커스텀 에러 생성에 사용됨 | throw new Error("기본 에러") |
TypeError | 타입이 잘못되었을 때 발생 | null.f(), "str"() |
ReferenceError | 정의되지 않은 변수에 접근 시 발생 | console.log(x) (x가 정의되지 않음) |
SyntaxError | 문법이 잘못되었을 때 발생 | eval("foo bar") |
RangeError | 값이 허용 범위를 벗어났을 때 발생 | new Array(-1) |
EvalError | eval() 사용 관련 오류 (거의 발생 안함) | (특정 환경에서만 발생) |
URIError | encodeURI, decodeURI에 잘못된 인자 사용 시 발생 | decodeURI("%") |
또한 사용자가 Error의 새로운 종류를 만들어 정의하는 것도 가능하다.
class의 extends 를 통하여 Error의 클래스를 상속하여 정의한다.
// 사용자 정의 예외 타입
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'
}
}
try - catch는 기본적으로 동기적으로 동작한다. 그래서 setTimeout처럼 비동기적으로 처리되는 함수에서 발생한 예외는 try-catch문에서 잡아낼 수 없다.
try {
setTimeout(function() {
noSuchVariable; // 스크립트는 여기서 죽습니다.
}, 1000);
} catch (e) { // 스크립트는 이미 멈췄기 때문에, 비동기 환경에서 뭐가 됬든 작동 안함.
alert( "작동 멈춤" );
}
setTimeout에 넘겨진 익명 함수는 엔진이 try..catch를 떠난 다음에서야 실행되기 때문이다. 따라서 스케줄 된 함수 내부의 예외를 잡으려면, try..catch를 반드시 함수 내부에 구현해야 한다.
setTimeout(function() {
try {
noSuchVariable; // 이제 try..catch에서 에러를 핸들링 할 수 있습니다!
} catch {
alert( "에러를 잡았습니다!" );
}
}, 1000);