[7장] 비동기 호출
7.1 API 요청
- fetch로 API 요청하기
- 서비스 레이어로 분리하기
- fetch 함수를 호출하는 부분이 서비스 레이어로 이동하고, 컴포넌트는 서비스 레이어의 비동기 함수를 호출하여 그 결과를 받아와 렌더링하는 흐름
- Axios 활용하기
- Axios를 사용하면 fetch의 번거로운 부분을 해소할 수 있다.
- Axios 인터셉터 사용하기
- 각각의 requester별로 다른 헤더를 설정해줘야 하는 로직이 필요할 수 있다.
- 이때 인터셉터 기능을 사용하여 requester에 따라 비동기 호출 내용을 추가해서 처리할 수 있다.
- API 응답 타입 지정하기
export interface Response<T> {
data: T;
status: string;
serverDateTime: string;
errorCode?: string; // FAIL, ERROR errorMessage?: string; // FAIL, ERROR
}
const fetchCart = (): AxiosPromise<Response<FetchCartResponse>> =>
apiRequester.get<Response<FetchCartResponse>>('cart');
const postCart = (postCartRequest: PostCartRequest): AxiosPromise<Response<PostCartResponse>> =>
apiRequester.post<Response<PostCartResponse>>('cart', postCartRequest);
- 뷰 모델 사용하기
- API 응답은 변할 가능성이 크므로 뷰 모델을 사용하여 API 변경에 따른 범위를 한정해줄 수 있다.
interface JobListResponse {
jobItems: JobListItemResponse[];
}
class JobListItem {
constructor(item: JobListItemResponse) {
/* JobListItemResponse에서 JobListItem 객체로 변환해주는 코드 */
}
}
class JobList {
readonly totalItemCount: number;
readonly items: JobListItemResponse[];
constructor({ jobItems }: JobListResponse) {
this.totalItemCount = jobItems.length;
this.items = jobItems.map(item => new JobListItem(item));
}
}
const fetchJobList = async (filter?: ListFetchFilter): Promise<JobListResponse> => {
const { data } = await api
.params({ ...filter })
.get('/apis/get-list-summaries')
.call<Response<JobListResponse>>();
return new JobList(data);
};
- Superstruct를 사용해 런타임에서 응답 타입 검증하기
- Spuerstruct 라이브러리를 사용하면 런타임에서의 데이터 유효성 검사를 통해 개발자와 사용자에게 자세한 에러를 보여줄 수 있다.
- 인터페이스 정의와 자바스크립트 데이터 유효성 검사를 쉽게할 수 있다.
import { assert, is, validate, object, number, string, array } from 'superstruct';
const Article = object({
id: number(),
title: string(),
tags: array(string()),
author: object({
id: number()
})
});
const data = {
id: 34,
title: 'Hello World',
tags: ['news', 'features'],
author: {
id: 1
}
};
assert(data, Article);
is(data, Article);
validate(data, Article);
- assert는 유효하지 않을 경우 에러를 던진다.
- is는 유효성 검사 결과에 따라 true 또는 false 즉, boolean 값을 반환한다.
- validate는 [error, data] 형식의 튜플을 반환한다. 유효하지 않을 때는 에러 값이 반환되고 유효한 경우에는 첫 번째 요소로 undefined, 두 번째 요소로 data value가 반환된다.
7.2 API 상태 관리하기
- 상태관리 라이브러리에서 호출하기 (Redux)
- 훅으로 호출하기 (react-query나 useSwr 같은 훅을 사용하여 호출할 수 있다.)
7.3 API 에러 핸들링
-
타입 가드 활용하기
- Axios 라이브러리에서는 isAxiosError 라는 타입가드를 제공한다.
function isServerError(error: unknown): error is AxiosError<ErrorResponse> { return axios.isAxiosError(error); }
- 서버에서 전달하는 공통 에러 객체에 대한 타입을 정의할 수도 있다.
interface ErrorResponse { status: string; serverDateTime: string; errorCode: string; errorMessage: string; }
-
에러 서브클래싱하기
- 실제 요청을 처리할 때 단순한 서버 에러도 발생하지만 인증 정보 에러, 네트워크 에러, 타임아웃 에러 같은 다양한 에러가 발생하기도 한다.
- 이를 더욱 명시적으로 표시하기 위해 서브클래싱을 활용할 수 있다.
- 서브클래싱은 기존(상위 또는 부모) 클래스를 확장하여 새로운(하위 또는 자식) 클래스를 만드는 과정을 말한다.
- 새로운 클래스는 상위 클래스의 모든 속성과 메서드를 상속받아 사용할 수 있고 추가적인 속성과 메서드를 정의할 수도 있다.
-
인터셉터를 활용한 에러 처리
- Axios 같은 페칭 라이브러리는 인터셉터를 제공한다.
const httpErrorHandler = (error: AxiosError<ErrorResponse> | Error): Promise<Error> => {
error => {
// 401 에러인 경우 로그인 페이지로 이동
if (error.response && error.response.status === 401) {
window.location.href = `${backOfficeAuthHost}/login?targetUrl=${window.location.href}`;
}
return Promise.reject(error);
};
};
orderApiRequester.interceptors.response.use((response: AxiosResponse) => response, httpErrorHandler);
- react-query를 활용한 에러 처리
- isError, isPending 등을 이용한 처리
7.4 API 모킹
- JSON 파일 불러오기
- json 파일을 만들거나 ts 파일안에서 export 해주어 불러올 수 있다.
- NextApiHandler 활용하기
- Next.js에서는 NextApiHandler를 활용할 수 있다.
- 이 경우 하나의 파일 안에 하나의 핸들러를 디폴트 익스포트로 구현해야 한다.
// api/mock/brand
import { NextApiHandler } from 'next';
const BRANDS: Brand[] = [
{
id: 1,
label: '배민스토어'
},
{
id: 2,
label: '비마트'
}
];
const handler: NextApiHandler = (req, res) => {
// request 유효성 검증
res.json(BRANDS);
};
export default handler;
- API 요청 핸들러에 분기 추가하기
- API 요청 함수에서 if문을 추가하여 선택적으로 모킹할 수 있다.
const mockFetchBrands = (): Promise<FetchBrandsResponse> =>
new Promise(resolve => {
setTimeout(() => {
resolve({
status: 'SUCCESS',
message: null,
data: [
{
id: 1,
label: '배민스토어'
},
{
id: 2,
label: '비마트'
}
]
});
}, 500);
});
const fetchBrands = () => {
if (useMock) {
return mockFetchBrands();
}
return requester.get('/brands');
};
- MSW 등을 사용하는 방안도 존재한다.