7장 에러 처리
2021. 07. 24
1. return null
function ask() {
return prompt('When is your birthday?');
}
function isValidDate(date: Date) {
return Object.prototype.toString.call(date) === '[object Date]' && !Number.isNaN(date.getTime())
}
function parse(birthday: string): Date {
const date = new Date(birthday)
if (isValidDate(date) {
return date;
}
return null;
}
const date = parse(ask());
if (date) {
console.log('Date is', date.toISOString());
} else {
consoel.error('Error parsing date for some reason');
}
-
Pros
- 타입 안정성을 유지하면서 에러를 처리하기 가장 간단함
-
Cons
- 문제가 생긴 원인을 알 수 없다.
- return type이 null이므로 타입 조합이 어려워진다.
- 모든 연산에서 Null을 체크해야 한다. 이로인해 연산이 중첩되거나 연결 코드가 지저분해 진다.
2. throw exception
function parse(birthDay: string): Date {
const date = new Date(birthDay);
if (isValidDate(date) {
return date
}
throw new RangeError('Enter a date in the form YYYY/MM/DD');
}
try {
const date = parse(ask());
console.info('Date is', date.toISOString());
} catch (e) {
if (e instanceof RangeError) {
console.error(e.message);
} else {
throw e
}
}
class InvalidDateFormatError extends RangeError {}
class DateIsInTheFutureError extends RangeError {}
function parse(birthDay: string): Date {
const date = new Date(birthDay);
if (!isValidDate(date) {
throw new InvalidDateFormatError('Enter a date in the form YYYY/MM/DD');
}
if (date.getTime() > Date.now()) {
throw new DateIsInTheFutureError('Invalid date range');
}
return date
}
try {
const date = parse(ask());
console.info('Date is', date.toISOString());
} catch (e) {
if (e instanceof InvalidDateFormatError) {
console.error(e.message);
} else if (e instance of DateIsInTheFutureError) {
console.warn(e.message);
} else {
throw e
}
}
- throw exception을 사용해서 문제에 대한 대처 및 디버깅에 도움이 되는 메타데이터를 얻을 수 있다.
- 처리하지 않은 에러는
e
는 다시 throw 한다. - 에러를 서브클래싱하여 더 구체적으로 표현하여 에러를 구분할 수 있다.
3. return exception
function ask() {
const text = prompt("When is your birthday?")
return text || ""
}
function isValidDate(date: Date) {
return (
Object.prototype.toString.call(date) === "[object Date]" &&
!Number.isNaN(date.getTime())
)
}
const date = parse(ask())
if (date) {
console.log("Date is", date.toISOString())
} else {
console.error("Error parsing date for some reason")
}
class InvalidDateFormatError extends RangeError {}
class DateIsInTheFutureError extends RangeError {}
function parse(
birthDay: string
): Date | InvalidDateFormatError | DateIsInTheFutureError {
const date = new Date(birthDay)
if (!isValidDate(date)) {
throw new InvalidDateFormatError("Enter a date in the form YYYY/MM/DD")
}
if (date.getTime() > Date.now()) {
throw new DateIsInTheFutureError("Invalid date range")
}
return date
}
const result = parse(ask())
if (result instanceof InvalidDateFormatError) {
console.error(result.message)
} else if (result instanceof DateIsInTheFutureError) {
console.warn(result.message)
} else {
console.info(result.toISOString())
}
4. Option
함수형 프로그래밍 간단히 맛보기
class Box {
constructor(value) {
this.$value = value
}
static of(value) {
return new Box(value)
}
map(fn) {
return new Box(fn(this.$value))
}
}
const addFive = num => {
return num + 5
}
// 첫 글자를 대문자로 바꿔줍니다.
const startCase = str => {
return str.charAt(0).toUpperCase() + str.slice(1)
}
Box.of(1).map(addFive) // Box(6)
Box.of(1)
.map(addFive)
.map(addFive) // Box(11)
Box.of("hello, FP").map(startCase) // Box('Hello, FP')
class Maybe {
constructor(value) {
this.$value = value
}
static of(value) {
return new Maybe(value)
}
get isNothing() {
return this.$value === null || this.$value === undefined
}
map(fn) {
return this.isNothing ? this : Maybe.of(fn(this.$value))
}
toString() {
return this.isNothing ? "Nothing" : `Just(${this.$value})`
}
}
const books = [
{ id: "book1", title: "coding with javascript" },
{ id: "book2", title: "speaking javaScript" },
]
const getById = id => arr => arr.filter(v => v.id === id)
const getBookById = (id, books) => Maybe.of(books).map(getById(id))
getBookById("book1", books)
getBookById("book2", books)
getBookById("book3", books)
class Either {
constructor(value) {
this.$value = value
}
static right(value) {
return new Right(value)
}
static left(value) {
return new Left(value)
}
}
class Right extends Either {
get isRight() {
return true
}
get isLeft() {
return false
}
map(fn) {
return new Right(fn(this.$value))
}
}
class Left extends Either {
get isRight() {
return false
}
get isLeft() {
return true
}
map(fn) {
return this
}
}
특수 목적 데이터 타입을 사용해 예외를 표현하는 방법도 존재한다.
-
Pros
- 에러가 발생할 수 있는 계산에 여러 연산을 연쇄적으로 수행할 수 있게 된다.
-
Cons
- 특수 목적 데이터 타입을 사용하지 않는 다른 코드와 호환이 되지 않음
// ...
function parse(birthday: string): Date[] {
const date = new Date(birthday)
if (!isValid(date)) return []
return [date]
}
const date = parse(ask())
date.map(toISOString).forEach(_ => console.info("Date is"))
⇒ if, prompt
always success, parse
always fail
function ask() {
const res = prompt("When is your birthday?")
if (!res) return []
return [result]
}
ask()
.map(parse)
.map(date => date.toISOString()) // error
.forEach(date => console.info("Date is", date))
To solve
⇒ Container 라는 특수한 데이터 타입에 담아서 상황을 해결하자
이를 해결하기 위한 방법으로, Option을 사용
-
Option
- Some
와 None 이 구현하게 될 인터페이스 - Some, None <: Option
- Option은 타입이기도 하고 함수이기도 하다.
- Some
interface Option<T> {}
class Some<T> implements Option<T> {
constructor(private value: T) {}
}
// 결과 값을 표현하기 위한 타입 및 값
class None implements Option<never> {} // 연산 실패를 나타내는 값
⇒
Option
Some
None = [];
interface Option<T> {
flatMap<U>(f: (value: T) => None): None
flatMap<U>(f: (value: T) => Option<U>): Option<U>
getOrElse(value: T): T
}
function Option<T>(value: null | undefined): None
function Option<T>(value: T): Some<T>
function Option<T>(value: T): Option<T> {
if (value === null || typeof value === "undefined") return new None()
return new Some(value)
}
class Some<T> extends Option<T> {
constructor(private value: T) {}
flatMap<U>(f: (value: T) => None): None
flatMap<U>(f: (value: T) => Some<U>): Some<U>
flatMap<U>(f: (value: T) => Option<U>): Option<U> {
return f(this.value)
}
getOrElse(): T {
return this.value
}
}
// 결과 값을 표현하기 위한 타입 및 값
class None extends Option<never> {
flatMap<U>(): None {
return this
}
getOrElse(value: U): U {
return value
}
} // 연산 실패를 나타내는 값