커스텀 에러와 에러 확장 개발하다면 자체 에러를 만드는 것이 직관적이기 때문에 필요할 때가 있다.
직접 에러 클래스를 만든다면 name, message 프로퍼티를 만들어야 하고 가능하다면 stack 프로퍼티도 지원해야 한다. 물론 추가 프로퍼티 사용은 무엇이든 가능하다.
throw
인수엔 무엇이든 사용 가능해서 꼭 Error
객체를 상속받아야 하는것은 아니지만, Error
객체를 상속받으면 obj instance Error
를 사용하여 에러 여부를 확인하는것이 가능하기 때문에 그냥 만드는 것보다 Error
객체를 상속받아서 만드는것을 추천한다.
에러 확장하기 user 정보를 읽는 readUser(json)
을 만들것이다.
readUser(json)
은 JSON.parse()
를 내부적으로 사용할거라 형식에 맞지 않으면 SyntaxError
발생.
하지만 user라면 반드시 가져야 할 name 이나 age 같은 속성이 없을때는 이런 에러를 던지면 안된다. 따로 데이터를 검증할 것인데 이때 발생하는 에러를 validationError
라고 만들것이다.
먼저 Error
를 상속 받기 전에 어떤 객체인지 살펴보자.
1 2 3 4 5 6 7 8 class Error { constructor (message ) { this .message = message; this .name = "Error" ; this .stack = <call stack > ; // stack은 표준은 아니지만, 대다수 환경이 지원합니다. } }
상속 받기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class ValidationError extends Error { constructor (message ) { super (message); this .name = "ValidationError" ; } } function test ( ) { throw new ValidationError("에러 발생!" ); } try { test(); } catch (err) { alert(err.message); alert(err.name); alert(err.stack); }
message
프로퍼티는 부모 생성자의 의해 설정된다.
name 프로퍼티는 ‘Error’ 로 설정되는데 원하는 이름으로 재설정 해준다.
이제 readUser(json)
안에서 ValidationError
를 사용해 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class ValidationError extends Error { constructor (message ) { super (message); this .name = "ValidationError" ; } } function readUser (json ) { let user = JSON .parse(json); if (!user.age) { throw new ValidationError("No field: age" ); } if (!user.name) { throw new ValidationError("No field: name" ); } return user; } try { let user = readUser('{ "age": 25 }' ); } catch (err) { if (err instanceof ValidationError) { alert("Invalid data: " + err.message); } else if (err instanceof SyntaxError ) { alert("JSON Syntax Error: " + err.message); } else { throw err; } }
에러 유형에 맞게 처리해 주었다.
에러 유형은 instanceof 말고도 err.name
으로 확인 가능하다.
더 깊게 상속하기 ValidationError
는 너무 포괄적이니 필요한 프로퍼티가 없는 경우에 상세한 에러를 만들어서 상속 하도록 한다.
PropertyRequiredError
만들기.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class ValidationError extends Error { constructor (message ) { super (message); this .name = "ValidationError" ; } } class PropertyRequiredError extends ValidationError { constructor (property ) { super ("No property: " + property); this .name = "PropertyRequiredError" ; this .property = property; } } function readUser (json ) { let user = JSON .parse(json); if (!user.age) { throw new PropertyRequiredError("age" ); } if (!user.name) { throw new PropertyRequiredError("name" ); } return user; } try { let user = readUser('{ "age": 25 }' ); } catch (err) { if (err instanceof ValidationError) { alert("Invalid data: " + err.message); alert(err.name); alert(err.property); } else if (err instanceof SyntaxError ) { alert("JSON Syntax Error: " + err.message); } else { throw err; } }
Error
클래스를 상속받아 직접 커스텀한 에러들은 this.name
을 수동으로 할당해 주고 있다.
이런 방법은 상당히 귀찮은 작업이 될 수 있다.
이런걸 피하기 위해서는 기본 에러 클래스를 만들고 상속 받으면 된다.
기본 에러 클래스는 this.name = this.constructor.name
을 추가해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class MyError extends Error { constructor (message ) { super (message); this .name = this .constructor.name; } } class ValidationError extends MyError { }class PropertyRequiredError extends ValidationError { constructor (property ) { super ("No property: " + property); this .property = property; } } alert( new PropertyRequiredError("field" ).name );
예외 감싸기 readUser
가 점점 커지면 에러도 다양해 질 것이고 VallidationError
는 이런 에러를 포함해야 할 것이다.
그런데 이렇게 에러를 추가하다보면 처리하는 곳에서 분기별로 처리하는 코드를 늘려야 할까?
대부분의 경우 그렇지 않다. 데이터를 읽었을때 에러 발생 여부만 알려주고 필요할때 세부 내용을 알려주는 방버을 사용한다.
wrapping exception을 해보자.
ReadError 만들기
ValidationError, SyntaxError 등의 에러는 readUser 내부에서 잡고 이때 ReadErrorㄹㄹ 생성.
ReadError 객체의 cause 프로퍼티엔 실제 에러에 대한 참조 저장.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class ReadError extends Error { constructor (message, cause ) { super (message); this .cause = cause; this .name = 'ReadError' ; } } class ValidationError extends Error { }class PropertyRequiredError extends ValidationError { }function validateUser (user ) { if (!user.age) { throw new PropertyRequiredError("age" ); } if (!user.name) { throw new PropertyRequiredError("name" ); } } function readUser (json ) { let user; try { user = JSON .parse(json); } catch (err) { if (err instanceof SyntaxError ) { throw new ReadError("Syntax Error" , err); } else { throw err; } } try { validateUser(user); } catch (err) { if (err instanceof ValidationError) { throw new ReadError("Validation Error" , err); } else { throw err; } } } try { readUser('{잘못된 형식의 json}' ); } catch (e) { if (e instanceof ReadError) { alert(e); alert("Original error: " + e.cause); } else { throw e; } }
이런 기법은 객체 지향 프로그래밍에서 널리 쓰이는 패턴임.
과제1 내장된 SyntaxError 클래스를 상속하는 FormatError 클래스를 만들어 봅시다.
만들어진 클래스에서 message, name, stack를 참조할 수 있어야 합니다.