Skip to content

Axios

Promise based HTTP client for the browser and node.js

Categories

Interceptors

client.interceptors.response.use(res => {
  if (res.data.status === false) {
    // Error message is retrived from the JSON body.
    const error = new Error(res.data.message);
    // Attach the response instance, in case we would like to access it.
    error.response = res;

    throw error;
  }

  // If the status is true, simply return back the response instance.
  return res;
});

Status Code 를 에러로 만드는 방법

Using the validateStatus config option, you can define HTTP code(s) that should throw an error.

axios.get('/user/12345', {
  validateStatus: function (status) {
    return status < 500; // Resolve only if the status code is less than 500
  }
})

Await request

async function getUser() {
  try {
    const response = await axios.get('/user?ID=12345');
    console.log(response);
  } catch (error) {
    console.error(error);
  }
}

Simple example

Performing a POST request:

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  })
  .then(function () {
    // always executed
  });

Performing multiple concurrent requests:

function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

Promise.all([getUserAccount(), getUserPermissions()])
  .then(function (results) {
    const acct = results[0];
    const perm = results[1];
  });

Interceptors 를 Skip 하고싶을 경우

axios.interceptors.response.use(undefined, err => {
  if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
    refreshLogin(getRefreshToken(),
      success => {
        setTokens(success.access_token, success.refresh_token)
        err.config.__isRetryRequest = true
        err.config.headers.Authorization = 'Bearer ' + getAccessToken()
        axios(err.config)
      },
      error => { console.log('Refresh login error: ', error) }
    )
  }
})

개인적으론, err.config에 새로운 Property 를 추가하기 보단, error.config.headers에 추가하면 타입 심볼이 부서지지 않으므로 좀 더 선호한다. (서버에 해당 헤더를 보내지 않으려면 방법이 없긴 하지만...)

아니면, 동일한 axios.create()를 사용하여 새로운 인스턴스를 만들자. (이러면 테스트할 때, axios-mock-adapter같은 mocking 객체에 인스턴스를 전달해야 정상적인 테스트가 가능한데,,, 이것도 영~;;;)

Form 전송 (application/x-www-form-urlencoded)

기본적으로 axios는 JavaScript 객체를 'JSON'으로 직렬화(serialize) 합니다. application/x-www-form-urlencoded 포멧 대신 데이터를 보내려면 다음 옵션 중 하나를 사용할 수 있습니다.

브라우저

브라우저에서 다음과 같이 URLSearchParams API를 사용할 수 있습니다.

const params = new URLSearchParams();
params.append('param1', 'value1');
params.append('param2', 'value2');
axios.post('/foo', params);

NOTE

URLSearchParams는 모든 브라우저에서 지원되지는 않지만, 사용할 수 있는 polyfill (url-search-params)이 있습니다.

또는 qs 라이브러리를 사용하여 데이터를 인코딩 할 수 있습니다.

const qs = require('qs');
axios.post('/foo', qs.stringify({ 'bar': 123 }));

ES6+ 사용하는 경우는 아래와 같이 작성할 수 있습니다.

import qs from 'qs';
const data = { 'bar': 123 };
const options = {
  method: 'POST',
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
  data: qs.stringify(data),
  url,
};
axios(options);

Node.js

다음과 같이 querystring 모듈을 사용할 수 있습니다.

const querystring = require('querystring');
axios.post('http://something.com/', querystring.stringify({ foo: 'bar' }));

물론 qs 라이브러리를 사용할 수도 있습니다.

const qs = require('qs');
axios.post('http://something.com/', qs.stringify({ foo: 'bar' }));

NOTE

Node.js의 querystring 메소드는 중첩된 객체를 문자열화 하는데 문제점이 있습니다. 중첩된 객체를 문자열화 해야할 경우가 잦을 경우 qs 라이브러리 사용이 권장됩니다.

Parameter Serializer

qs 모듈을 사용하면 편하다.

import axios from 'axios';
import qs from 'qs';

axios.defaults.baseURL = process.env.REACT_APP_BASE_URL || window.location.origin;
axios.defaults.headers.post['Accept'] = 'application/json';
axios.defaults.paramsSerializer = params => {
  return qs.stringify(params)
};

Axios로 JWT Refresh 토큰 갱신

let isTokenRefreshing = false;
let refreshSubscribers = [];

const onTokenRefreshed = (accessToken) => {
  refreshSubscribers.map((callback) => callback(accessToken));
};

const addRefreshSubscriber = (callback) => {
  refreshSubscribers.push(callback);
};

axios.interceptors.response.use(
  (response) => {
    return response;
  },
  async (error) => {
    const {
      config,
      response: { status },
    } = error;
    const originalRequest = config;
    if (status === 401) {
      if (!isTokenRefreshing) {
        // isTokenRefreshing이 false인 경우에만 token refresh 요청
        isTokenRefreshing = true;
        const refreshToken = await AsyncStorage.getItem("refreshToken");
        const { data } = await axios.post(
          `http://localhost:3000/refresh/token`, // token refresh api
          {
            refreshToken,
          }
        );
        // 새로운 토큰 저장
        const {
          accessToken: newAccessToken,
          refreshToken: newRefreshToken,
        } = data;
        await AsyncStorage.multiSet([
          ["accessToken", newAccessToken],
          ["refreshToken", newRefreshToken],
        ]);
        isTokenRefreshing = false;
        axios.defaults.headers.common.Authorization = `Bearer ${newAccessToken}`;
        // 새로운 토큰으로 지연되었던 요청 진행
        onTokenRefreshed(newAccessToken);
      }
      // token이 재발급 되는 동안의 요청은 refreshSubscribers에 저장
      const retryOriginalRequest = new Promise((resolve) => {
        addRefreshSubscriber((accessToken) => {
          originalRequest.headers.Authorization = "Bearer " + accessToken;
          resolve(axios(originalRequest));
        });
      });
      return retryOriginalRequest;
    }
    return Promise.reject(error);
  }
);

See also

Favorite site

References


  1. Maruzzings_devlog_-_axios_interceptors_token_refresh.pdf