에러가 발생하면 스크립트는 즉시 중단되고, 콘솔에 에러가 출력된다.
그러나 try..catch 문법을 사용하면 스크립트가 중단되는 걸 방지하고, 에러를 잡아서(catch) 더 합당한 무언가를 할 수 있게 된다.
try..catch는 오직 런타임 에러에만 동작
- try..catch는 실행 가능한 코드(유효한 자바스크립트 코드)에만 동작한다.
try..catch는 동기적으로 동작
- setTimedout처럼 '스케줄 된' 코드에서 발생한 예외는 try..catch에서 잡아낼 수 없다.
에러 객체
에러가 발생하면 자바스크립트는 에러 상세내용이 담긴 객체를 생성하고 그 후 catch 블록에 이 객체를 인수로 전달한다.
try{
// ...
} catch(err){ -> '에러 객체', err 대신 다른 이름으로도 쓸 수 있음
// ...
}
내장 에러 전체와 에러 객체는 두 가지 주요 속성을 가진다.
name - 에러 이름. 정의되지 않은 변수 때문에 발생한 에러라면 "ReferenceError"가 이름이 된다.
message - 에러 상세 내용을 담고 있는 문자 메시지
stack - 현재 호출 스택. 에러를 유발한 중첩 호출들의 순서 정보를 가진 문자열로 디버깅 목적으로 사용된다.
try..catch 사용하기
JSON으로 인코딩된 값을 읽을 수 있도록 해주는 JSON.parse(str) 메서드는 주로 서버 등에서 네트워크를 통해 전달받은 데이터를 디코딩하는 데 사용한다.
let json = '{"name":"John", "age": 30}'; // 서버로부터 전달받은 데이터
let user = JSON.parse(json); // 전달받은 문자열을 자바스크립트 객체로 변환
// 문자열 형태로 전달받은 user가 프로퍼티를 가진 객체가 됨
alert(user.name); // John
alert(user.age); // 30
잘못된 형식의 json이 들어온 경우, JSON.parse는 에러를 만들기 때문에 스크립트가 '죽는다'.
서버에서 전달받은 데이터가 잘못되어 스크립트가 죽는 경우, 사용자는 개발자 콘솔을 열지 않는 이상 절대 원인을 알 수 없습니다. 그런데 사람들은 메시지 등을 통해 에러의 원인을 알지 못한 채 무언가가 '그냥 죽는 것'을 정말 싫어한다.
try..catch를 사용해 이를 처리하면
let json = "{ bad json}";
try{
let user = JSON.parse(json);
alert(user.name);
}catch(e){
// 에러가 발생하면 제어 흐름이 catch 문으로 넘어온다.
alert("데이터에 에러가 있어 재요청을 시도합니다.");
alert(e.name);
alert(e.message);
}
위 예시와 같이 catch 블록 안에서 새로운 네트워크 요청 보내기, 사용자에게 대안 제안하기, 로깅 장치에 에러 정보 보내기 등과 같은 구체적인 일을 할 수 있다. 스크립트가 죽는 것보다 훨씬 나은 대응이다.
직접 에러를 만들어서 던지기
json이 문법적으로 잘못되진 않았지만, 스크립트 내에서 사용 중인 필수 프로퍼티 name을 가지고 있지 않다면 아래와 같은 상황이 발생한다.
let json = '{ "age":30 }';
try{
let user = JSON.parse(json); // <-- 에러 없음
alert(user.name); // 이름이 없다.
} catch (e) {
alert('실행되지 않는다.');
}
JSON.parse는 정상적으로 실행되었지만 name이 없는 건 에러를 유발하는 상황이다.
throw 연산자
throw 연산자는 에러를 생성한다.
이론적으로는 어떤 것도 에러 객체로 사용할 수 있지만 내장 에러와의 호환을 위해 되도록 에러 객체에 name과 message 프로퍼티를 넣어주는 것을 권장한다.
자바스크립트는 Error, SyntaxError, ReferenceError, TypeError 등의 표준 에러 객체 관련 생성자를 지원한다. 이 생성자들을 이용해 에러 객체를 만들 수도 있다.
let error = new Error(message);
// or
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...
내장 생성자를 사용해 만든 내장 에러 객체의 naem 프로퍼티는 생성자 이름과 동일한 값을 갖는다. 프로퍼티 message의 값은 인수에서 가져온다.
let error = new Error('에러가 발생했습니다.');
alert(error.name) // Error
alert(error.message) // 에러가 발생했습니다.
잘못된 데이터를 받았을 때, JSON.parser가 어떤 종류의 에러를 만들어내는지 아래 코드를 통해 살펴보자.
try{
JSON.parse("{ 잘못된 형식의 json }");
} catch(e) {
alert(e.name) // SyntaxError
alert(e.message); // Unexpected token b in JSON at position 2
}
SyntaxError가 발생한다.
사용자를 나타내는 객체에 name 프로퍼티는 반드시 있어야 하므로, 이제 name이 없으면 에러가 발생한 것으로 간주하고 예외 처리해보자.
throw 연산자를 사용해 에러를 던져보자.
let json = '{ "age":30 }'; // 불완전한 데이터
try{
let user = JSON.parse(json);
if(!user.name) {
throw new SyntaxError('불완전한 데이터: 이름 없음'); // (*)
}
alert(user.name);
} catch(e) {
alert( "JSON Error: " + e.message); // JSON Error: 불완전한 데이터: 이름 없음
}
(*)로 표시한 줄에서 throw 연산자는 message를 이용해 SyntaxError를 생성한다. 에러 생성 방식은 자바스크립트가 자체적으로 에러를 생성하는 방식과 동일하다. 에러가 발생했으므로 try의 실행은 즉시 중단되고 제어 흐름이 catch로 넘어간 것을 alert 창을 통해 확인할 수 있다.
이제 JSON.parse에서 에러가 발생한 경우를 포함해서 모든 에러를 catch 블록 안에서 처리할 수 있다.
에러 다시 던지기
위에서 불완전한 try..catch로 처리하였는데 또 다른 예기치 않은 에러가 try {...} 블록 안에서 발생 할 수도 있다. 정의되지 않은 변수 사용 등의 프로그래밍 에러가 발생할 가능성은 항상 있다.
let json = '{ "age":30 }'; //불완전한 데이터
try{
let user = JSON.parse(json);
//
} catch(err) {
alert("JSON Error: " + err); // JSON Error: RefereceError: user is not defined
// 실제론 JSON Error가 X
}
위 예시에서 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("불완전한 데이터: 이름 없음");
}
blabla(); // 예상치 못한 에러
alert(user.name);
} catch(e) {
if (e instanceof SyntaxError) {
alert( "JSON Error: " + e.message);
} else {
throw e; // 에러 다시 던지기 (*)
}
}
catch 블록 안의 (*)로 표시한 줄에서 다시 던져진 에러는 try..catch '밖으로 던져집니다.' 이때 바깥에 try..catch가 있다면 여기서 에러를 잡습니다. 아니라면 스크립트는 죽을 겁니다.
이렇게 하면 catch 블록에선 어떻게 다룰지 알고 있는 에러만 처리하고, 알 수 없는 에러는 '건너뛸 수' 있습니다.
try..catch를 하나 더 만들어, 다시 던져진 예상치 못한 에러를 처리해보면
function readData() {
let json = '{ "age": 30 }';
try {
// ...
blabla(); // 에러
if (!(e instanceof SyntaxError)) {
throw e; // 알 수 없는 에러 다시 던지기
}
}
}
try {
readData();
} catch (e) {
alert( "External catch got: "+ e ); // 에러를 잡음
}
readData는 SyntaxError만 처리할 수 있지만, try..catch에서는 예상치 못한 에러도 처리할 수 있게 되었다.
'Javascript > Node.js' 카테고리의 다른 글
콜백 (0) | 2021.09.27 |
---|---|
커스텀 에러와 에러 확장 (0) | 2021.09.27 |
메서드와 this (0) | 2021.09.27 |
ESLint (0) | 2021.09.13 |
async/await (0) | 2021.09.13 |