Axios?
사진처럼, fetch api와 axios api를 비교하는 것은 백포도, 적포도를 비교하는 것일지도 모르겠습니다. 각자의 풍미가 있고 특성이 있는 것처럼, fetch와 axios도 각각의 매력이 있으니 상황에 맞게 좋은 방식을 선택하는 것이 좋겠습니다.
MIT 라이선스를 가지는 axios api는 fetch()에서 느낄 수 있던 아쉬운 점들을 어느 정도 해소해 주는 방식들을 가지고 있습니다. 형태와 기본 틀은 거의 같은 맥락을 유지하지만, 데이터 형태 변환과 에러 처리에 있어서 더욱 효과적인 방식을 선택할 수 있습니다.
axios() function
axios() function은 npm / yarn를 통해서 package.json에 추가하는 것으로 사용 할 수 있습니다.
<!-- npm install -->
$ npm install axios
<!-- yarn install -->
$ yarn add axios
Promise객체를 반환하는 함수는 다음과 같습니다.
let promise = axios(url[, config ]);
fetch와 동일한 형태를 가지고 있으나, axios는 shorthand methods를 제공하고 있어서, fetch api의 사용에서 options를 { method: 'POST', body: new FormData(myForm) }와 같은 형태로 사용하는 것을 대신할 수 있습니다. 해당 방식은 redux-thunk에서 하나의 ACTION에 대해서 ${ACTION_TYPE}_PENDING, ${ACTION_TYPE}_FUFILED, ${ACTION_TYPE}_REJECTED와 같은 상태에 따른 케이스를 직접 작성하는 것을, redux-pender / redux-promise-middleware를 사용하는 것을 통해 onSuccess()와 같은 메서드들을 사용함으로써 대신한 것과 같은 방식입니다.
axios.post('/ansrlm.tistory.com/login', {
id: 'moonkey',
password: 'myPassword'
});
shorthand methods를 사용하는 것으로 axios(url, [options])를 사용할 때 options을 찾아가며 어떤 기능을 하는지를 확인하는 것 보다 빠르게 이해를 할 수 있도록 돕습니다. 또한 별칭 메서드를 사용하는 것으로, config에서 url / method / data에 대한 속성을 지정하지 않고 사용하게 됩니다. 위 예시에서 id / password는 사실 다음과 같은 방식으로 구성된 것입니다.
axios({
url: '/ansrlm.tistory.com/login',
method: 'post',
data: {
id: 'moonkey',
password: 'myPassword'
}
});
axios.post 이외에도 다음과 같은 메서드들이 존재합니다.
- axios.request(config)
- axios.head(url[, config])
- axios.options(url[, config])
request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
head<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
options<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
getUri(config?: AxiosRequestConfig): string;
CRUD에 해당하는 부분들은 fetch와 동일하게 구성되어 있고, 그 사용 방법이 명확하지만, request / head / options에 대한 것은 없어서, typescript에 import 해서 그 interface에 대해서 찾아볼 수 있었습니다.
head는 message body가 없는 GET request입니다.
const response = await axios.head('/ansrlm.tistory.com/lists');
console.log(`response status: ${response.status}`);
console.log(`response status: ${response.headers[Content-Type]}`);
response에 대한 status와, header에 구성해 둔 정보들을 가져 올 수 있습니다.
options도 axios의 OPTIONS이라는 method로 작동하는데, 해당 부분은 GET request를 하기 이전에 domain과 request를 위한 headers를 통해서 허락된 상태인지를 확인하는 것을 위해서 사용합니다. (developer.mozilla.org/en-US/docs/Web/HTTP/CORS#functional_overview)
추가적으로, getUri도 존재하는데, request에 해당하는 uri를 string타입으로 반환하는 것이 존재합니다.
- axios.get(url[, config])
const axios = require('axios');
dispatch({ type: LOADING_SPINNER, show: true });
axios.get('/ansrlm.tistory.com/posts?category=IT&tag=axios')
.then(response => { console.log(response); })
.catch(error => { console.log(error); })
.then(() => { dispatch({ type: LOADING_SPINNER, show: false }); });
// same expression
dispatch({ type: LOADING_SPINNER, show: true });
axios.get('/ansrlm.tistory.com/posts', {
params: {
category: 'IT',
tag: 'axios',
} })
.then(response => { console.log(response); })
.catch(error => { console.log(error); })
.then(() => { dispatch({ type: LOADING_SPINNER, show: false }); });
config의 params를 사용하는 것으로, queryString을 대신할 수 있습니다.
- axios.post(url[, data[, config]])
- axios.put(url[, data[, config]])
- axios.patch(url[, data[, config]])
- axios.delete(url[, config])
Config options
{
url: string
method?: string
| 'get' | 'GET'
| 'delete' | 'DELETE'
| 'head' | 'HEAD'
| 'options' | 'OPTIONS'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
| 'purge' | 'PURGE'
| 'link' | 'LINK'
| 'unlink' | 'UNLINK'
// default : GET
baseURL?: string
// 상대 URL을 설정해서 axios가 url을 간편하게 적는 것을 수행할 수 있습니다.
// url = 'lists', baseUrl = '/ansrlm.tistory.com/'를 합쳐서 '/ansrlm.tistory.com/lists'로 사용
headers?: object
// { Content-Type: 'application/json' }
params?: object
// queryString에 해당하며, 요청과 함께 전송 될 URL 매개 변수입니다.
// { id: 'moonkey', password: 'myPassword' }
// 사실 로그인의 부분은 sql injection 때문에 queryString이 아닌, body에 들어가는 것이 맞습니다.
body?: object
// 요청의 본문 body parameters에 해당하는 데이터 입니다.
// { id: 'moonkey', password: 'myPassword' }
// only for 'PUT' / 'POST' / 'PATCH'
// 'transformRequest`가 설정되지 않은 경우 다음 유형 중 하나여야 합니다.
// - [ string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams ]
// - 브라우저 전용: FormData, File, Blob
// - Node.js 전용: Stream, Buffer
transformRequest?: [function (data, headers) {
return data;
}]
// request parameters에 대한 사전 type casting 등 전처리를 할 수 있습니다.
responseType?: string
| 'arraybuffer'
| 'blob'
| 'document'
| 'json'
| 'text'
| 'stream'
// default : JSON
auth?: object
// fetch api에서 사용하는 credentials에 해당합니다. 기존의 Authorization을 over-write합니다.
// { id: 'moonkey', password: 'myPassword' }
}
자주 사용하게 되는 request config에 대해서 소개드렸으며, github.com/axios/axios#request-config에 모든 config에 대한 부분이 존재하니 필요에 따라서 확인하시면 되겠습니다.
Config Default settings
매 axios call마다 앞서 설명드린 config options을 선언하다 보면 중복된 코드들이 생기는 상황이 많습니다. 특히 baseURL의 경우 외부 서비스를 호출하지 않는다면 계속 같은 값을 기계적으로 넣을 수밖에 없습니다. 그래서 모든 axios request에 global 적용할 수 있는 default setting을 하는 것으로 중복된 코드를 줄일 수 있습니다. 또한 사용자 정의 인스턴스 생성이 가능합니다.
axios.defaults.baseURL = '/ansrlm.tistory.com';
axios.defaults.timeout = 10000;
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; // 해당 부분은 token에 맞게 적용합니다.
axios.defaults.headers.post['Content-Type'] = 'application/json';
// same expression
const instance = axios.create(
{
baseURL: '/ansrlm.tistory.com',
timeout: 10000,
headers: { Content-Type: 'application/json' }
}
);
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
defualt setting과 axios method를 사용하면서 config를 선언하는 것은 CSS에서 !Important -> Author Style -> User Style -> Browser Style의 우선순위처럼 순번이 정해져 있습니다.
axios method의 config -> axios.defaults의 config -> lib/defaults.js에 있는 라이브러리 기본 config으로 우선순위를 가지고 있습니다. 그래서 baseURL의 경우를 예로 들자면, defaults에 서비스 안에서 사용하는 baseURL을 설정해 두고, 특정 외부 request를 하게 된다면, 그때 axios.get의 method: baseURL: 'externalURL'처럼 사용을 할 수 있습니다.
Response Schema
{
data: object
// response data에 해당하며, ex) data.object.myObjectKey와 같이 해당 부분 하위로 server가 구성한 object에 따라 접근이 가능
status: string
// HTTP status code에 해당하며, 해당 부분은 2번 글에 있습니다.
statusText: string
// statusText는 fetch api에서 response.ok와 같이 접근했던 부분처럼, ex) 'OK'와 같이 반환됩니다.
headers: object
// request에서 선언한 header('Content-Type')와 같은 것은 소문자('content-type')로 반환됩니다.
config: object
request: object
}
const response = await axios.get('/ansrlm.tistory.com/lists');
console.log(response.data.totalCount);
console.log(response.status === '200');
console.log(response.statusText === 'OK');
console.log(response.headers);
console.log(response.config);
console.log(response.request);
await를 통해서 response에 직접 접근해서 사용도 가능하며, .then을 통해서 응답을 받거나 fetch api()에서 사용한 try-then-catch와 같은 방식으로 에러 처리를 할 수 있습니다.
Intercept / Reject / Abort
fetch API에서 try - then - then ... - catch를 하는 format보다 더욱 효과적이고, 하지 못했던 기능들을 axios API는 제공하고 있습니다.
인터셉터
then / catch의 scope로 들어가서 처리되기 이전에 특정 작업이나 처리를 함으로써 요청이나 응답을 가로챌 수 있습니다.
export interface AxiosInterceptorManager<V> {
use(onFulfilled?: (value: V) => V | Promise<V>, onRejected?: (error: any) => any): number;
eject(id: number): void;
}
const requestInterceptor = axios.interceptors.request.use(
onFulfilled: (config) => {
console.log(config);
// do something other treatments or actions
return config;
},
onRejected: (error) => {
console.log(error);
// do something other treatments or actions
return Promise.reject(error);
}
);
const responseInterceptor = axios.interceptors.response.use(
onFulfilled: (response) => {
console.log(response);
// do something other treatments or actions
return response;
},
onRejected: (error) => {
console.log(error);
// do something other treatments or actions
return Promise.reject(error);
}
);
axios.interceptors.request.eject(requestInterceptor); // this is for deleting interceptor
axios.interceptors.response.eject(responseInterceptor); // this is for deleting interceptor
해당 부분은 사용자 정의 인스턴스에도 적용이 가능합니다.
오류 처리, 요청 취소
fetch API에서 부드럽게 제공하지 못했던 부분을 axios API에서는 catch(error)의 세분화와, cancel token을 통해서 요청을 취소할 수 있습니다.
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
// this cancel token can be used other method without initialization procedure like 'new()'
axios.get('/ansrlm.tistory.com/lists', { cancelToken: source.token })
.then((response) => console.log(response)
.catch((error) => {
if(axios.isCancel(error)) {
console.log('cancelation occurs', error.message);
} else {
if(error.response) {
// meaning that request & response are done, but response statusCode is !200
console.log(error.response.data);
console.log(error.response.status);
...
} else if(error.request) {
// meaning that request is done, but response is not
console.log(error.request);
} else {
console.log('normal(?) error occurs', error);
}
}
});
source.cancel('my error message is this: cancelation activates');
source.cancel을 통해서 하나의 토큰으로 여러개의 요청을 abort 할 수 있습니다. 또한 error.response의 default setting은 2xx의 범위가 아닌 것을 지칭하는데, 해당 부분도 config option을 설정하는 것으로 변경할 수 있습니다.
axios.post('/ansrlm.tistory.com/login', {
body: { id: 'moonkey', password: 'myPassword' },
validateStatus: (status) => {
return status < 300;
}
});
※ 잘못된 부분이나 궁금한 점 댓글로 작성해 주시면 최대한 답변하겠습니다. 읽어주셔서 감사합니다.
※ REST API #5 - Fetch vs Axios에서 fetch / axios API의 비교를 해볼 예정입니다.
'React > Rest API' 카테고리의 다른 글
REST API #5 - Axios vs Fetch ? (0) | 2021.02.27 |
---|---|
REST API #3 - Fetch (1) | 2021.02.07 |
REST API #2 - 개념 정리 (2) (0) | 2021.01.12 |
REST API #1 - 개념 정리 (1) (0) | 2021.01.10 |