본문 바로가기
프론트엔드

[프론트엔드] ajax & axios

by ISA(류) 2021. 9. 16.

개인적으로 늘 잘하는 시니어 개발자가 없는 환경에서 홀로 모든 걸 주도적으로 판단하고 처리 해온 입장에서

학습과 평가를 위해서 코딩 테스트, 과제 테스트 등의 기회가 있을 때마다 주기적으로 참여해왔다.

과제 테스트의 경우 검색이 허용 되는 경우가 대부분이지만 레퍼런스를 제외하고는 검색을 제한하는 방식으로

(모르는 게 있거나 기억나지 않으면 그냥 0에서부터 구현하기)

 

주로 참가해왔는데 이번 프로그래머스 데브 매칭의 경우 바닐라 js로 기본적인 dom제어와 클라이언트 라우팅 그리고 ajax를 아예 처음부터 만들어야 했다. 그 과정에서 ajax를 통해서 fetch로 기본적인 api형태를 만들다가 엄청 헤매면서 시간을 허비하게(대략 20분) 되는데 평소 ajax의 경우 axios를 통해서 처리하다 보니 fetch 같은 기본 제공되는 인터페이스가 조금 낯설었던 게 원인이었다.

 

axios라는 라이브러리가 잘 만들어진 api이다 보니 내부 구성에 대해서 알 필요가 전혀 없고, lib사용법에 대해서 간단히 찾아서 사용할 수 있다 보니 ajax 요청에 대한 세세한 디테일에 대한 앎이 흐릿해졌다는 점을 느꼈고, 개인적으로 바닐라 js를 이용한 ajax를 간단하게나마 구조화해보는 게 좋을 거 같다는 생각이 들어서 구조화해본다.

 

처음 axios를 생각해서 바닐라 js로 구조화 해본 api다. 이름은 간단히 rxios라고 이름 지었다.

class Rxios {
    constructor() {
        this._url = "https://isa-myblog.herokuapp.com/post/";
    }
    async _rxios(api) {
        try {
            const response = await fetch(api);
            const data = await response.json();
            return data;
        } catch (e) {
            console.error(e);
        }

    }
    async getTags() {
        const result = await this._rxios(`${this._url}tags`);
        console.log(result);
    }
}

const api = new Rxios();
api.getTags();

코드 자체는 간단하고 상속이라는 특징을 잘 표현하기 위해서 class 문법을 사용했다. 각 uri에 따른 메서드를 만들고 그 메서드에 맞는 요청 처리를 하며 공통 로직의 경우 내부 메서드로 추상화했다. 다만 간단히 만들고 나니 ajax핸들링과 api핸들링 사이의 관심사가 다름을 느껴서 관심사 분리를 통한 개선을 진행했다.

class Rxios {
    constructor() {

    }
    async _rxios(api, method, data) {
        try {
            const request = await fetch(api, {
                method: method,
                body: data
            });
            const response = await request.json();
            return response;
        } catch (e) {
            console.error(e);
        }

    }
    async get(url) {
        return await this._rxios(url, "GET");
    }
    async post(url, data) {
        return await this._rxios(url, "POST", data);
    }
}

class Api {
    constructor() {
        this.rxios = new Rxios();
        this._url = "https://isa-myblog.herokuapp.com/post/";
    }
    async getTags() {
        const result = await this.rxios.get(`${this._url}tags`);
        console.log(result);
    }
}

const api = new Api();
api.getTags();

간단하게나마 aixos와 비슷한 구조의 ajax 로직을 만들고 그를 통해서 api요청을 핸들링하는 코드이다. ajax와 api라는 두 관심사를 분리해서 기본적인 구조를 만들고 요구사항에 따라서 기본적인 기능들(쿠키, 다른 메서드들, 에러 핸들링이나, 해더 등)을 rxios 클래스에서 추가하고, api 요청의 경우 api 클래스에서 만드는 그런 간단한 구조.. 솔직히 실무에서 직접 ajax를 핸들링할 케이스는 거의 없기에 이 정도면 충분하지만, 문득 호기심이 생겼다. axios는 어떻게 구성되어 있을까?

 

그래서 깃허브에서 axios repo를 fork해서 코드를 분석해봤다. 간단하고 직관적이더라. (가독성을 위해서 포스팅을 쪼갤까 조금 고민해봤으나 귀찮아서 생략)

axios/lib 디렉토리 view

axios는 흔한 npm 패키지들이 그렇듯이 패키지의 api를 사용자에게 제공하는 형식에 대한 내용이고 (별로 중요하지는 않다.)

defaults.js의 경우 axios.defaults 즉 기본 설정이다. 사용자가 아무것도 추가적인 정보를 주지 않을 시 기본적으로 사용되는 설정에 대해서 담고 있다. 물론 해당 설정 역시 사용자가 임의로 수정 가능하다.

cancel dir의 경우 axios cancel 기능들, helpers dir는 헬퍼 함수 그리고 utils.js는 그런 헬퍼 함수들이나 타입 체크 등의 말 그대로 유틸, core가 axios 핵심 로직들 그리고 adapters가 aixos의 통신 방식을 담당한다. 일일이 해당 부분들을 포스팅하자니 너무 길어지니, 간략히 말하면 axios 자체는(core) promise 핸들링에 대한 내용이 주 내용이었고, adapters의 내용이 바로 ajax 요청에 대한 내용이었다. 

axios/lib/adapters

따로 사용자가 어뎁터 패턴에 따른 어뎁터를 설정하는게 아니라면 axios의 기본 제공 adapters는 2개로 각각 http와 xhr이다. default 설정에서 확인할 수 있듯이 브라우저에서는 xhr를 node에서는 http adapters를 사용한다.

axios/lib/defaults.js

이쯤에서 눈치 챘겠지만.. axios는 그렇다. 놀랍게도 fetch를 사용하지 않는다.(적어도 현시점까지는 이번 주에 fork 한 것이니)

axios/lib/adapters/xhr.js

axios는 가장 기본적인 XHLHttpRequest를 통해서 ajax를 주고 받는다. 이게 axios가 크로스 브라우저등에 있어서 잘 되어있는 이유다.

사실 제이쿼리도 뜯어봤을때 axios와 구조가 비슷했던걸 보면 프런트단의 js 고전 아키텍처는 대부분 저런 구조로 되어 있다고 봐도 될 거 같다. 대부분 var을 사용하고 클로저나 모듈 시스템을 이용한 데이터를 은닉한다는 공통점을 가지고 있다. ㅎㅎ

 

왜 fetch를 안쓰는걸까? 라고 자문해보니 몇 가지 답이 나왔다. "크로스 브라우징을 위해서", "호환성"을 위해서

등등...  그러나,그냥 fetch나 let, const가 나온지 얼마 안 되었다는 게 더 정답일 거 같다.

굳이 잘 만들어진 소스를 뜯어고쳐서 얻을 매리트도 없었을거고, 보안적으로 이슈가 있다거나 그런 것도 없으니까 말이다.

 

axios  추상화

axios - ajax & promise 핸들링

props | defaults

defaults : ajax 전반의 기본 config

props: 해당 ajax 요청의 config

반응형