Part7_AsynchronousCall
장경은
책 읽기

[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 등을 사용하는 방안도 존재한다.