이번 포스팅에서는 프론트엔드에서 'try...catch' 문을 통해 처리하는 예외처리 작성 패턴과 사용 처에 따른 Error 객체의 생성에 대해 다룹니다.
1. 예제 구성
일반적으로 UI에서 저장할 때 발생하는 프로세스 중 발생하는 오류를 에제로 사용 합니다.
아래 그림에서
1) No : 저장하려는 자료에 대한 유효성 검사 실패 (날짜형, 문자형 등)
2) Fail : Ajax 등과 같은 Service로 전달하는 통신과정에서의 오류 (400, 500번대의 기타 오류들)
3) Fail : 정상응답(200)을 회신 받았으나, Service 단의 저장과정에서 발생한 예외처리로 정상처리되지 않은 경우의 실패
2. Custom Error
1) BaseError: Error Class를 상속받아 'catch' 구문으로 발생한 Error 객체를 다룸.
2) FormatError: 입력값의 자료형에 대한 오류를 다루기 위한 클래스
3) LogicalError: 비즈니스 로직상 적합하지 않은 자료 행을 구분하기 위한 에러 클래스
4) APISendError: API 송/수신간의 오류를 구분하기 위한 에러 클래스
5) SaveError: 상기 상세 구분을 위한 3가지 Error Class를 감싸기 위한 에러 클래스
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 | class BaseError extends Error { constructor(message) { super(message); this.name = this.constructor.name; } } class FormatError extends BaseError { constructor(message, seq, property) { super(message); this.seq = seq; this.property = property; } } class LogicalError extends BaseError { constructor(message, seq, property, category){ super(message); this.seq = seq; this.property = property; this.category = category; } } class APISendError extends BaseError { constructor(message, url, code){ super(message); this.url = url; this.code = code; } } class SaveError extends BaseError { constructor(message){ super(message); } } |
- 3 : 상위 클래스인 'Error' 로의 message 값 반영
- 4 : BaseError를 상속할 객체들의 생성 클래스 구분. (instanceof 구문으로 ErrorClass 구분)
- 11 : 예외처리 과정에서 입력값의 식별값(seq)을 Error객체에 포함
- 12: 예외처리 과정에서 입력값의 속성인자(property)을 Error객체에 포함
- 21 : 예외처리 과정에서 입력값의 유형인자(category)을 Error객체에 포함
- 28 : API 전송 URL
- 29 : API 오류처리 과정에서 식별할 오류코드
3. 호출 구조
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 | /** 유효성 검사 부 */ const Validation = (rowData) => { /** Write Validation Logic */ throw } /** 데이터 전송 부 */ const SendData = (sendData) => { /** Write API Send Logic */ throw } /** '저장' 로직 */ const SaveData = (sourceData) => { /** 1. 유효성 검사 */ try{ sourceData.map(row => Validation(row)); } catch(error) { if(error instanceof FormatError){ throw new SaveError(); } else if (error instanceof LogicalError) { throw new SaveError(); } else { throw error; } } /** 2. API 전송 (Save) */ try{ SendData(sourceData); } catch(error){ if(error instanceof APISendError){ throw new SaveError(); } } } /** main */ try{ const sourceData = [ { seq: '001', product: 'apple', price: '1000', expireDt: '2023-12-31', soldOut: false}, { seq: '002', product: 'banaa', price: '2000', expireDt: '2024-01-00', soldOut: false}, { seq: '003', product: 'melon123', price: '4000', expireDt: '2033-10-01', soldOut: true}, { seq: '004', product: 'grape', price: '1500', expireDt: '2023-11-15', soldOut: false}, { seq: '005', product: 'cherry', price: '4400', expireDt: '2023-11-15', soldOut: true}, ]; SaveData(sourceData); }catch(e){ if(e instanceof SaveError) { alert(e.message); } else { throw e; } } |
- 41 : 샘플 자료형. 행 단위로 과일상품에 대한 정보를 관리함.
- 49 : 저장 함수 호출
- 22 : "1) 유효성 검사" 수행.
- 25 : 자료형 검사 (FormatError)에 대한 예외처리
- 26 : 논리형 검사 (LogicalError)에 대한 예외처리
- 27 : 정의된 두 가지 검사 외의 예외처리
- 32 : "2) 데이터 전송" 수행.
- 35 : 데이터 전송 간의 오류 (APISendError)에 대한 예외처리
- 52 : "25, 26, 35" 라인에서 감싸놓은 저장에 대한 오류 (SaveError)에 대한 포괄적인 예외처리
4. 유효성 검사 정의 및 예외처리
정규식을 통해 아래와 같이 유효성검사 여부에 따라, 예외를 발생시킨다.
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 | const DEFAULT_REGEXP_ALPHABET = /^[a-zA-Z]+$/; // 영문자(알파벳) const DEFAULT_REGEXP_ALPHANUM = /^[0-9a-zA-Z]+$/; // 영문자 + 숫자 const DEFAULT_REGEXP_FIGURE = /^[0-9]+$/; // 숫자 const DEFAULT_REGEXP_DATE = /^(19[7-9][0-9]|20\d{2})-(0[0-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/; // 날짜 /** 유효성 검사 부 */ const Validation = (rowData) => { if(!(DEFAULT_REGEXP_ALPHABET).test(rowData.product)){ throw new FormatError(`자료유형 오류 : 영문자(알파벳)`, rowData.seq, rowData.product ); } if(!(DEFAULT_REGEXP_FIGURE).test(rowData.price)){ throw new FormatError(`자료유형 오류 : 숫자`, rowData.seq, rowData.price ); } if(!(DEFAULT_REGEXP_DATE).test(rowData.expireDt)){ throw new FormatError(`자료유형 오류 : 날짜`, rowData.seq, rowData.expireDt); } if(rowData.soldOut){ const category = `STOCK`; throw new LogicalError(`논리유형 오류`, rowData.seq, rowData.soldOut ? '품절' : '재고', category); } } |
- 9 : 제품명은 영문으로만 구성.
- 13 : 가격은 숫자로만 구성.
- 18 : 날짜는 yyyy-MM-dd 유형으로 구성.
- 21 : 품절여부 검사.
5. 데이터 전송 부 예외처리
axios 전송에 대한 성공(then)에서의 처리실패에 대한 예외처리와 실패(catch)에서의 기타 통신상의 오류에서 예외를 발생시킨다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** 데이터 전송 부 */ const SendData = (sendData) => { const DATA_SAVE_API = 'http://test.com'; axios.post(`${DATA_SAVE_API}`, {params: sendData}) .then((response) => { /** Success Process */ if(response.state = 'invalid'){ throw new APISendError('유효하지 않은 응답', DATA_SAVE_API, response.code); } }) .catch((error) => { throw new APISendError('전송 실패', DATA_SAVE_API, ''); }) } |
6. 저장 부, 예외처리 감싸기와 메시징 처리
유효성 검사, 데이터 전송 과정에서 발생한 예외를 감싸고 메시징처리하여 핸들링 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /** '저장' 로직 */ const SaveData = (sourceData) => { /** 1. 유효성 검사 */ try{ sourceData.map(row => Validation(row)); } catch(error) { if(error instanceof FormatError){ throw new SaveError(`[${error.message}]\n행(SEQ): ${error.seq}\n입력값: ${error.property}`); } else if (error instanceof LogicalError) { throw new SaveError(`[${error.message}]\n행(SEQ): ${error.seq}\n입력값: ${error.property}\n구분: ${error.category}`); } else { throw error; } } /** 2. API Send (Save) */ try{ SendData(sourceData); } catch(error){ if(error instanceof APISendError){ throw new SaveError(`[API Error]\nCode: ${error.code}\nMessage: ${error.message}`); } } } |
7. 전체 코드
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | const DEFAULT_REGEXP_ALPHABET = /^[a-zA-Z]+$/; // 영문자(알파벳) const DEFAULT_REGEXP_ALPHANUM = /^[0-9a-zA-Z]+$/; // 영문자 + 숫자 const DEFAULT_REGEXP_FIGURE = /^[0-9]+$/; // 숫자 const DEFAULT_REGEXP_DATE = /^(19[7-9][0-9]|20\d{2})-(0[0-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/; // 날짜 class BaseError extends Error { constructor(message) { super(message); this.name = this.constructor.name; } } class FormatError extends BaseError { constructor(message, seq, property) { super(message); this.seq = seq; this.property = property; } } class LogicalError extends BaseError { constructor(message, seq, property, category){ super(message); this.seq = seq; this.property = property; this.category = category; } } class APISendError extends BaseError { constructor(message, url, code){ super(message); this.url = url; this.code = code; } } class SaveError extends BaseError { constructor(message){ super(message); } } (() => { /** 유효성 검사 부 */ const Validation = (rowData) => { if(!(DEFAULT_REGEXP_ALPHABET).test(rowData.product)){ throw new FormatError(`자료유형 오류 : 영문자(알파벳)`, rowData.seq, rowData.product ); } if(!(DEFAULT_REGEXP_FIGURE).test(rowData.price)){ throw new FormatError(`자료유형 오류 : 숫자`, rowData.seq, rowData.price ); } if(!(DEFAULT_REGEXP_DATE).test(rowData.expireDt)){ throw new FormatError(`자료유형 오류 : 날짜`, rowData.seq, rowData.expireDt); } if(rowData.soldOut){ const category = `STOCK`; throw new LogicalError(`논리유형 오류`, rowData.seq, rowData.soldOut ? '품절' : '재고', category); } } /** 데이터 전송 부 */ const SendData = (sendData) => { const DATA_SAVE_API = 'http://test.com'; axios.post(`${DATA_SAVE_API}`, {params: sendData}) .then((response) => { /** Success Process */ if(response.state = 'invalid'){ throw new APISendError('유효하지 않은 응답', DATA_SAVE_API, response.code); } }) .catch((error) => { throw new APISendError('전송 실패', DATA_SAVE_API, ''); }) } /** '저장' 로직 */ const SaveData = (sourceData) => { /** 1. 유효성 검사 */ try{ sourceData.map(row => Validation(row)); } catch(error) { if(error instanceof FormatError){ throw new SaveError(`[${error.message}]\n행(SEQ): ${error.seq}\n입력값: ${error.property}`); } else if (error instanceof LogicalError) { throw new SaveError(`[${error.message}]\n행(SEQ): ${error.seq}\n입력값: ${error.property}\n구분: ${error.category}`); } else { throw error; } } /** 2. API Send (Save) */ try{ SendData(sourceData); } catch(error){ if(error instanceof APISendError){ throw new SaveError(`[API Error]\nCode: ${error.code}\nMessage: ${error.message}`); } } } try{ const sourceData = [ { seq: '001', product: 'apple', price: '1000', expireDt: '2023-12-31', soldOut: false}, { seq: '002', product: 'banaa', price: '2000', expireDt: '2024-01-00', soldOut: false}, // { seq: '003', product: 'melon123', price: '4000', expireDt: '2033-10-01', soldOut: true}, { seq: '004', product: 'grape', price: '1500', expireDt: '2023-11-15', soldOut: false}, { seq: '005', product: 'cherry', price: '4400', expireDt: '2023-11-15', soldOut: true}, ]; SaveData(sourceData); }catch(e){ if(e instanceof SaveError) { alert(e.message); } else { throw e; } } }); |
0 댓글