에러를 처리하는 시점? 어디가 더 좋은가
: API 공통화한 모듈에서 처리하는 것 보다는 컴포넌트 단에서 에러를 처리하고, 사용자에게 알려주는 것이 더 좋은 방법이라는 것을 알게되었다.(캡틴판교님 강의 질문 중에서)
API 상태를 저장하는 데이터
: fetches, processes라는 객체를 생성, API 함수 별로 해당하는 프로퍼티를 만들고 해당 프로퍼티의 상태를 변경해서 API의 성공, 로딩, 에러에 대한 상태를 처리했다.
// API함수의 상태를 담는 객체
data() {
return {
fetches: {
goods: SUCCESS_CODE,
},
}
},
methods: {
// API 요청 함수
async fetchGoods() {
try {
// 요청 시작
this.fetches.goods = LOADING_CODE;
// 요청 성공 시
this.fetches.goods = SUCCESS_CODE;
} catch (error) {
// 에러 발생 시
this.fetches.goods = ERROR_CODE;
}
},
- API 상태에 따라 보여줄 화면 지정
<!-- goods/index.vue -->
<!-- 로딩 상태일 때 보여지는 영역 -->
<div v-if="fetches.goods === LOADING_CODE">loading...</div>
<!-- 요청 성공 상태일 때 보여지는 영역 -->
<div v-else-if="fetches.goods === SUCCESS_CODE">{{ goods }}</div>
<!-- 요청 실패 상태일 때 보여지는 영역 -->
<div v-else>Error!</div>
: 1번의 경우에 하나의 API당 한 개의 에러 컴포넌트가 필요하다. 따라서, 여러개의 API 함수의 상태를 공통으로 관리하는 방법이 필요했다.
우선, 페이지의 구조를 다음과 같이 바꿨다.
// goods/index.vue
data() {
return {
pageErrors: [],
// 페이지 새로고침 중 여부
isReloading: false,
fetches: {
// 기본 상태는 성공 상태
layouts: SUCCESS_CODE,
goods: SUCCESS_CODE,
},
}
},
computed: {
// processes 중에서 상태값이 에러인 프로퍼티가 있는지 체크
isProcessesError() {
if( !this.processes ) return false;
const valuesOfProcesses = Object.values(this.processes);
if( !valuesOfProcesses.length ) return;
return valuesOfProcesses.findIndex(value => value === ERROR_CODE) > -1;
},
// fetches 중에서 상태값이 에러인 프로퍼티가 있는지 체크
isFetchesError() {
if( !this.fetches ) return false;
const valuesOfFetches = Object.values(this.fetches);
return !!valuesOfFetches.length && valuesOfFetches.findIndex(value => value === ERROR_CODE) > -1;
},
// isProcessesError, isFetchesError중에서 하나라도 true인지 체크
isPageError() {
return this.isProcessesError || this.isFetchesError;
},
// Process, Fetches 중에 에러가 발생한 프로퍼티들을 배열로 리턴
getErrorPropertyName() {
if( !this.isPageError ) return [];
let names = [];
if( this.isProcessesError ) {
for( const key in this.processes ) {
if( this.processes[key] === ERROR_CODE ) {
names.push(key);
}
}
}
if( this.isFetchesError ) {
for( const key in this.fetches ) {
if( this.fetches[key] === ERROR_CODE ) {
names.push(key);
}
}
}
return names;
},
},
methods: {
async fetchLayouts() {
// ...
},
async fetchGoods() {
// ...
},
// pageErrors 배열에서 name에 해당하는 객체가 이미 존재하는지 체크
getErrorMethodIndex(name) {
return this.pageErrors.findIndex(o => o.methodName === name);
},
// pageErrors에 에러 객체 생성해서 푸시
pushErrorMethod(name, args) {
const _index = this.getErrorMethodIndex(name);
if( _index > -1 ) {
this.pageErrors.splice(_index, 1);
}
this.pageErrors.push({
methodName: name,
args,
});
},
// 에러가 있는 API 함수들 재 실행
async executeErrorMethods() {
const self = this;
if( !this.pageErrors.length ) return;
// this.pageErrors.forEach(o => {
// if( o.methodName && self[o.methodName] ) {
// var args = o.args || [];
// self[o.methodName].apply(self, args);
// }
// });
const pageErrors = this.pageErrors.filter(o => !!o.methodName && !!self[o.methodName]);
if( !pageErrors.length ) return;
// 페이지 새로고침 중 여부 true로 변경
this.isReloading = true;
// 참고: https://ko.javascript.info/promise-api
const requests = pageErrors.map(o => {
// FIXME 기본 값을 []로 할지 null로 할지
var args = o.args || [];
// .apply()를 통해서 context 지정 및 arguments 전달
// NOTE return을 하지 않을 경우 Promise.all()에 적용할 수 없다.
return self[o.methodName].apply(self, args);
});
// Promise.all을 통해 모든 요청이 끝날때까지 대기
await Promise.all(requests);
this.isReloading = false;
},
// 에러 발생 시 > 페이지 새로고침 버튼 클릭
reloadPage() {
this.executeErrorMethods();
},
},
<!-- goods/index.vue -->
<!-- 에러가 있을 경우 보여지는 컴포넌트 영역 -->
<error-component v-if="isPageError"></error-component>
<!-- 페이지 레이아웃 데이터를 가져오는 API 로딩 시 보여지는 영역 -->
<loading-component v-if="fetches.layouts === LOADING_CODE"></loading-component>
<!-- 페이지 레이아웃 데이터를 가져오는 API 성공 시 보여지는 영역 -->
<main-component v-else-if="fetches.layouts === SUCCESS_CODE"></main-component>
<!-- goods/index.vue -->
<!-- 에러가 있고, 새로고침 중이 아닐 경우 보여지는 컴포넌트 영역 -->
<error-component v-if="isPageError && !isReloading"></error-component>
<!-- 페이지 레이아웃 데이터를 가져오는 API 로딩 또는 새로고침 중일 경우 보여지는 영역 -->
<loading-component v-if="fetches.layouts === LOADING_CODE || isReloading"></loading-component>
<!-- 페이지 레이아웃 데이터를 가져오는 API 성공 시 보여지는 영역 -->
<main-component v-else-if="fetches.layouts === SUCCESS_CODE"></main-component>