지금까지 에러를 처리할 때 예상 가능한 에러를 try..catch 로 처리하였습니다. 그런데 또 다른 예기치 않은 에러가 try {..} 블록 안에서 발생 할 수도 있습니다. 정의되지 않은 변수 사용 등의 프로그래밍 에러가 발생할 가능성은 항상 있습니다.

let json = '{ "age": 30 }'; // 불완전한 데이터

try {
	user = JSON.parse(json); // <-- user 앞에 let 을 붙이는 걸 잊음.
	// ...
} catch (err) {
	alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
	// (실제로 JSON Error 가 아닙니다.)
}

에러는 어떤 상황에서도 발생할 수 있습니다. 위에선 ‘불완전한 데이터’를 다루려는 목적으로 try..catch 를 썼습니다. catch 는 원래 try 블록에서 발생한 모든 에러를 잡으려는 목적으로 만들어졌습니다.

그런데 위 예시에서 catch 는 예상치 못한 에러를 잡아내 주긴 했지만, 에러 종류와 관계없이 “JSON Error” 메시지를 보여줍니다. 이렇게 에러 종류와 관계없이 동일한 방식으로 에러를 처리하는 것은 디버깅을 어렵게 만들기 때문에 좋지 않습니다.

이런 문제를 피하고자 ‘다시 던지기(rethrowing)’ 기술을 사용합니다. 규칙은 간단합니다.

catch는 알고 있는 에러만 처리하고 나머지는 ‘다시 던져야’ 합니다.

  1. catch가 모든 에러를 받습니다.
  2. catch(err) {…} 블록 안에서 에러 객체 err 를 분석합니다.
  3. 에러 처리 방법을 알지 못하면 throw err 를 합니다.

보통 에러 타입을 instanceof 명령어로 체크합니다.

try {
	user = { /*...*/ };
} catch (err) {
	if(err instanceof ReferenceError) {
		alert('ReferenceError'); // 정의되지 않은 변수에 접근하면 'ReferenceError' 발생
	}
}

*err.name 프로퍼티로 에러 클래스 이름을 알 수도 있습니다. 기본형 에러는 모두 err.name 프로퍼티를 가집니다. 또는 err.constructor.name 을 사용할 수도 있습니다.*

에러를 다시 던져서 catch 블록에선 SyntaxError 만 처리되도록 해보겠습니다.

let json = '{ "age": 30 }'; // 불완전한 데이터
try {
	let user = JSON.parse(json);
	if(!user.name) {
		throw new SyntaxError("불완전한 데이터: 이름 없음");
	}
	test(); // 예상치 못한 에러

	alert(user.name);
} catch(err) {
	if(err instanceof SyntaxError) {
		alert("JSON Error: " + err.message);
	} else {
		throw err; // 에러 다시 던지기 (*)
	}
}