Fetch ๋ง๊ณ  Axios (Feat.Refresh Token, Context)

Fetch ๋ง๊ณ  Axios (Feat.Refresh Token, Context)

Refresh Token์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์š”์ฒญ๋“ค์ด ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌ๋˜์–ด์•ผ ํ–ˆ๋‹ค.
์—ฌ๊ธฐ์ €๊ธฐ ์‚ฐ์žฌ๋˜์–ด ์žˆ๋Š” fetch๋“ค์„ Axios๋ฅผ ์‚ฌ์šฉํ•ด ์‹น ๋ฐ”๊ฟ”๋ณธ ํ›„๊ธฐ.

1. Axios๊ฐ€ ๋ญ”๋ฐ?

Axios๋Š” JavaScript๋ฅผ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ HTTP ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ค‘ ํ•˜๋‚˜์ด๋‹ค.
๊ฐ„ํŽธํ•œ HTTP ์š”์ฒญ(GET, POST, PUT, DELETE)์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ณ , Promise ๊ธฐ๋ฐ˜์ด๊ธฐ ๋•Œ๋ฌธ์— ๋น„๋™๊ธฐ ์ž‘์—…์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

? ๊ทธ๊ฑด fetch๋„ ์ž–์•„์š”

๋งž๋‹ค.

๊ฐ„๋‹จํ•œ HTTP ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•ด์•ผํ•œ๋‹ค๋ฉด fetch๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋˜์ง€๋งŒ, ๋ชจ๋“  ์š”์ฒญ์— ๋™์ผํ•œ ์„ค์ •(eg. baseURL, headers, etc)์„ ์ ์šฉํ•ด์•ผ ํ•œ๋‹ค๋“ ์ง€, ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋™์ผํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋“ ์ง€, ์ด๋Ÿฐ ์ค‘์•™ํ™”๋œ ๊ด€๋ฆฌ๋ฅผ ํ•˜๋ ค๋ฉด axios๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.
cf. HTTP ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋˜๋‹ค๋ฅธ JavaScript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ Superagent๊ฐ€ ์žˆ๋‹ค๋งŒ, ๋” ๋งŽ์ด ์‚ฌ์šฉ๋˜๊ณ  ๋” ์ž์ฃผ ์—…๋ฐ์ดํŠธ ๋˜๋Š” Axios๋ฅผ ๊ณจ๋ž๋‹ค.

๋ฟ๋งŒ ์•„๋‹ˆ๋ผ axios๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ž๋™์œผ๋กœ JSON ํŒŒ์‹ฑ๋„ ํ•ด์ฃผ๊ณ , ๋ณด๋‹ค ๊ฐ„๊ฒฐํ•œ ๋ฌธ๋ฒ•์œผ๋กœ ์š”์ฒญ๋“ค์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‚˜์˜ ๊ฒฝ์šฐ์—๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋™์ผํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ด์„œ axios๋ฅผ ๋„์ž…ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
๊ทธ ๊น€์— baseURL์ด๋‚˜ headers๋„ ๊ณตํ†ต์œผ๋กœ ์„ค์ •ํ•ด์ฃผ๋ฉฐ ์•ฑ ์—ฌ๊ธฐ์ €๊ธฐ์„œ ์‚ฌ์šฉํ•˜๋˜ fetch๋“ค์„ axios instance๋ฅผ ํ™œ์šฉํ•ด ์ „๋ถ€ ๋ฐ”๊ฟ”๋ณด์•˜๋‹ค.

2. Axios๋ฅผ ํ™œ์šฉํ•ด ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ

axios๋ฅผ ํ™œ์šฉํ•ด์„œ ํ† ํฐ์„ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด,

1.axios instance๋ฅผ ์ƒ์„ฑํ•˜๊ณ 

import axios from 'axios';

const instance = axios.create({
  baseURL: 'https://api.example.com',
  headers: {
    "Content-Type": "application/json",
    'Authorization': `Bearer ${accessToken}`,
  }
});

2.HTTP interceptor๋ฅผ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค.

instance.interceptors.request.use(
  async (config) => {
    const expirationTime = new Date(localStorage.getItem('expirationTime'));
    if (expirationTime && expirationTime < new Date()) {
      const newAccessToken = await refreshToken();
      config.headers.Authorization = `Bearer ${newAccessToken}`;
      localStorage.setItem('accessToken', newAccessToken);
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

async function refreshToken() {
  const refreshToken = localStorage.getItem('refreshToken');
  const response = await axios.post('https://auth.example.com/token', {
    grant_type: 'refresh_token',
    refresh_token: refreshToken
  });
  const { access_token } = response.data;
  const expirationTime = new Date();
  expirationTime.setSeconds(expirationTime.getSeconds() + response.data.expires_in);
  localStorage.setItem('accessToken', access_token);
  localStorage.setItem('expirationTime', expirationTime.getTime());
  return access_token;
}

์œ„ ์ฝ”๋“œ๋Š” ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์ „์— ํ† ํฐ์˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ํ™•์ธํ•˜๊ณ  ๋งŒ๋ฃŒ๋˜์—ˆ๋‹ค๋ฉด refreshToken์„ ์ด์šฉํ•ด ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๋Š” ์˜ˆ์‹œ์ด๋‹ค.

์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ณ ๋ คํ•ด์•ผํ•  ๊ฒƒ๋“ค์ด ์กฐ๊ธˆ ๋” ์žˆ๋‹ค.

์ฒซ์งธ, ์ด Instance๋ฅผ ์–ด๋–ป๊ฒŒ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•  ๊ฒƒ์ธ๊ฐ€
๋‘˜์งธ, ํ† ํฐ ์žฌ๋ฐœ๊ธ‰์„ ์ง„ํ–‰ํ•˜๋Š” ์ค‘ ์ƒ์„ฑ๋œ ๋˜๋‹ค๋ฅธ ์š”์ฒญ์— ๋Œ€ํ•ด ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•  ๊ฒƒ์ธ๊ฐ€

3. Axios Instance๋ฅผ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

์šฐ๋ฆฌ๊ฐ€ AutoRefreshToken๋ผ๋Š” function์— ์ธ์Šคํ„ด์Šค์™€ ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์ •์˜ํ–ˆ๋‹ค๊ณ  ํ•˜์ž.

๊ทธ๋Ÿผ ์ด ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ปดํฌ๋„ŒํŠธ๋งˆ๋‹ค ๋‹ค ํŒจ์Šคํ•ด์ฃผ๊ฑฐ๋‚˜,
์š”์ฒญ์ด ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ๋งˆ๋‹ค ๊ฐ€์„œ AutoRefreshToken์„ importํ•˜๊ณ , instance(์ดํ•˜ apiClient)๋ฅผ ์ด๋ ‡๊ฒŒ ๋ฐ›์•„์™€ ์จ์•ผํ•  ๊ฒƒ๋งŒ ๊ฐ™๋‹ค.

useEffect(() => {
    if (accessToken) {
      setApiClient(
        <AutoRefreshToken accessToken={accessToken} setAccessToken={setAccessToken} />
      );
    }
  }, [accessToken]);

์ข€ ๋” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ ์—†์„๊นŒ?

์žˆ๋‹ค.

Context

React์—๋Š” Context๋ผ๋Š” ๊ฒŒ ์žˆ๊ณ , ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ์—์„œ props๋ฅผ ํ†ตํ•ด ์ผ์ผ์ด ๋„˜๊ฒจ์ฃผ์ง€ ์•Š์•„๋„ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.
๋ฌด๋ ค React ๊ณต์‹ ๋ฌธ์„œ์— useState, useEffect์™€ ํ•จ๊ป˜ ๊ธฐ๋ณธ Hook์œผ๋กœ ๋ช…์‹œ๋˜์–ด ์žˆ๋Š” useContext๋ฅผ ์ด์šฉํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
์ด ์นœ๊ตฌ๋Š” ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ Depth๊ฐ€ ๊นŠ์„ ๋•Œ ํŠนํžˆ ์œ ์šฉํ•œ๋ฐ,

import React, { useState } from 'react';

function GrandchildComponent({ message }) {
  return <div>{message}</div>;
}

function ChildComponent({ message }) {
  return <GrandchildComponent message={message} />;
}

function ParentComponent({ message }) {
  return <ChildComponent message={message} />;
}

function App() {
  const [message, setMessage] = useState('Hello from the top level!');

  const changeMessage = () => {
    setMessage('Updated message from the top level!');
  };

  return (
    <div className="App">
      <h1>Deep Component Tree Example</h1>
      <button onClick={changeMessage}>Change Message</button>
      <ParentComponent message={message} />
    </div>
  );
}

export default App;

์ด๋Ÿฐ ์ฝ”๋“œ๊ฐ€ ์žˆ์„ ๋•Œ GrandchildComponent์—์„œ ๋ฉ”์„ธ์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด์„œ ParentComponene, ChildComponent์— Props๋กœ ๋„˜๊ฒจ ์ฃผ์–ด์•ผ๋งŒ ๊ฐ€๋Šฅํ–ˆ๋˜ ๊ฒƒ์„,

import React from 'react';
import { MessageProvider, useMessage } from './MessageContext';

function GrandchildComponent() {
  const { message } = useMessage();
  return <div>{message}</div>;
}

function ChildComponent() {
  return <GrandchildComponent />;
}

function ParentComponent() {
  return <ChildComponent />;
}

function App() {
  ...
  return (
    <MessageProvider>
      <div className="App">
        <h1>Deep Component Tree Example</h1>
        <button onClick={changeMessage}>Change Message</button>
        <ParentComponent />
      </div>
    </MessageProvider>
  );
}

export default App;

์ด๋Ÿฐ์‹์œผ๋กœ ํ•„์š”ํ•œ ๋ฐ์—์„œ๋งŒ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
์—ฌ๊ธฐ์„œ ๋ดค๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ค‘๊ฐ„ ๋‹จ๊ณ„์˜ ์ปดํฌ๋„ŒํŠธ์—๋„ Props๋ฅผ ๋„˜๊ฒจ์ค˜์•ผ ํ•˜๋Š” ๋ฌธ์ œ๋ฅผ Prop Drilling์ด๋ผ๊ณ  ๋ถ€๋ฅด๊ณ ,
์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Contex๋ฟ๋งŒ ์•„๋‹ˆ๋ผ Redux๋‚˜ MobX์™€ ๊ฐ™์€ ์ƒํƒœ ๊ด€๋ฆฌ ๋„๊ตฌ๊ฐ€ ๋งŽ์ด ์‚ฌ์šฉ๋œ๋‹ค.
Context๊ฐ€ ์•„๋‹Œ Redux๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ˆœ์ˆ˜ํ•จ์ˆ˜๋กœ๋งŒ ๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅํ•˜๊ณ  ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋ฉฐ Redux Dev Tool๋กœ ๋””๋ฒ„๊น…๋„ ์œ ์šฉํ•˜๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์œผ๋‚˜, ํ˜„์žฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ทœ๋ชจ๊ฐ€ ํฌ์ง€ ์•Š๊ณ  ๋ฐ์ดํ„ฐ ํ๋ฆ„์ด ๋ณต์žกํ•˜์ง€ ์•Š์•„ ์—ฌ๊ธฐ์„œ๋Š” Context๋ฅผ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•œ๋‹ค.

Context๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ฒซ๋ฒˆ์งธ๋กœ ํ•ด์•ผํ•  ์ผ์€ Context๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

import { createContext } from "react"

export const AuthContext = createContext(1)

๊ฐ„๋‹จํ•˜๋‹ค.

์ด๋ฅผ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋ ค๋ฉด

import React, { useEffect } from "react"

์•„๋งˆ ์ด ์ •๋„ import ๋˜์–ด์žˆ๋˜ ๊ตฌ๋ฌธ์—

import React, { useEffect, useContext } from "react"
import { AuthContext } from "../AutoRefreshToken"

const contextValue = useContext(AuthContext)

์ด ์ •๋„ ์ถ”๊ฐ€ํ•ด์„œ ์‚ฌ์šฉํ•ด์ฃผ๋ฉด ๋œ๋‹ค.
์ด๋Ÿฌ๋ฉด ์–ด๋””์„œ๋“  contextValue์— ์šฐ๋ฆฌ๊ฐ€ ์„ค์ •ํ•ด์คฌ๋˜ default ๊ฐ’์ธ 1์ด ๋“ค์–ด๊ฐ€๊ณ , ์ด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

์—ฌ๊ธฐ๋‹ค ์šฐ๋ฆฌ๊ฐ€ ์ƒ์„ฑํ•œ axios instance์ธ apiClient๋ฅผ ์ „๋‹ฌํ•˜๋ ค๋ฉด contetxt๋ฅผ ์ œ๊ณต(provide)ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

import { createContext } from "react"
import axios from "axios"

export const AuthContext = createContext()

export function AuthProvider({ children }) {
	const apiClient = axios.create({
		baseURL: "https://api.example.com",
		headers: {
			"Content-Type": "application/json",
			Authorization: `Bearer ${accessToken}`,
		},
	})

	apiClient.interceptors.request.use(
		async (config) => {
			const expirationTime = new Date(localStorage.getItem("expirationTime"))
			if (expirationTime && expirationTime < new Date()) {
				const newAccessToken = await refreshToken()
				config.headers.Authorization = `Bearer ${newAccessToken}`
				localStorage.setItem("accessToken", newAccessToken)
			}
			return config
		},
		(error) => {
			return Promise.reject(error)
		}
	)

	async function refreshToken() {
		const refreshToken = localStorage.getItem("refreshToken")
		const response = await axios.post("https://auth.example.com/token", {
			grant_type: "refresh_token",
			refresh_token: refreshToken,
		})
		const { access_token } = response.data
		const expirationTime = new Date()
		expirationTime.setSeconds(
			expirationTime.getSeconds() + response.data.expires_in
		)
		localStorage.setItem("accessToken", access_token)
		localStorage.setItem("expirationTime", expirationTime.getTime())
		return access_token
	}

	return (
		<AuthContext.Provider value={apiClient}>{children}</AuthContext.Provider>
	)
}

์ด๋Ÿฐ์‹์œผ๋กœ AuthProvider๋ฅผ ๋”ฐ๋กœ ๋–ผ์„œ ์–˜๊ฐ€ AuthContext.Provider์™€ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๊ฒŒ ํ•˜๊ณ ,
์šฐ๋ฆฌ๊ฐ€ ์•ฑ์„ ๋ Œ๋”๋งํ•˜๋Š” ๊ณณ(์•„๋งˆ๋„ App.js)์—์„œ ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ <AuthProvider>๋ฅผ ์ง‘์–ด๋„ฃ์–ด์ฃผ๋ฉด apiClient๋ฅผ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ ์–ด๋””์„œ๋‚˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

4. ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์ค‘ ์ƒ์„ฑ๋œ ์š”์ฒญ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ

์œ„ ์˜ˆ์‹œ์—์„œ๋Š” ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์ „ ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ํ™•์ธํ•ด์„œ ์žฌ๋ฐœ๊ธ‰ํ–ˆ์—ˆ์œผ๋‚˜,
์ด๋ฒˆ์—๋Š” ๋งŒ๋ฃŒ๋œ ์š”์ฒญ์ด ๋ณด๋‚ด์ ธ 401์ด ์‘๋‹ต์œผ๋กœ ์™”์„ ๋•Œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋กœ ์žฌ๋ฐœ๊ธ‰ํ•˜๋Š” ์˜ˆ์‹œ๋ฅผ ๊ฐ€์ ธ์™”๋‹ค.

apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response.status === 401) {
      const originalConfig = error.config
      //error.config.headers.authorizationToken = `Bearer ${newAccessToken}` ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ config object๊ฐ€ immutableํ•ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.
      if (!isRefreshing) {
        isRefreshing = true
        try {
          const response = await apiClient.post(
            `/auth/refresh-token`,
            {
              refreshToken: refreshToken,
            }
          )
          if (response && response.code === 201) {
            accessToken = response.accesstoken
            refreshToken = response.refreshtoken

            sessionStorage.setItem("accessToken", accessToken)
            sessionStorage.setItem("refreshToken", refreshToken)
            originalConfig.headers.authorizationToken = `Bearer ${accessToken}`

            failedRequests.forEach((f) => f(accessToken))
            failedRequests = []

            return apiClient.request(originalConfig)
          } else {
            window.location.href = "/login"
          }
        } catch (refreshError) {
          console.error("Error refreshing access token:", refreshError)
          window.location.href = "/login"
          throw refreshError
        } finally {
          isRefreshing = false
        }
      } else {
        const retryOriginalRequest = new Promise((resolve) => {
          failedRequests.push((token) => {
            originalConfig.headers.authorizationToken = `Bearer ${token}`
            resolve(apiClient.request(originalConfig))
          })
        })

        return retryOriginalRequest
      }
    } else if (error.response.status === 403) {
      window.location.href = "/login"
    }
  }
)

ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์ค‘ ์ƒ์„ฑ๋œ ์š”์ฒญ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ์žฌ๋ฐœ๊ธ‰์ด ๋˜๊ณ  ์žˆ๋Š” ์ƒํƒœ๋ฅผ isRefreshing์ด๋ผ๋Š” ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด ์„ค์ •ํ–ˆ๋‹ค.

์ด ์ฒ˜๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์œ ๋กœ ํ•„์š”ํ•˜๋‹ค.

๋งŒ๋ฃŒ๋œ ํ† ํฐ a์™€ ํ•จ๊ป˜ ๋ณด๋‚ด์ง„ ์š”์ฒญ A๊ฐ€ a' ๋ผ๋Š” refresh token๊ณผ ํ•จ๊ป˜ ์žฌ๋ฐœ๊ธ‰๋˜๋˜ ์ค‘, ๋งŒ๋ฃŒ๋œ ํ† ํฐ a๋กœ ์š”์ฒญ B๊ฐ€ ๋“ค์–ด์˜ค๊ณ  ์ด๋ฏธ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›์€ ํ† ํฐ b, b'๊ฐ€ ์žˆ์„ ๋•Œ a'๋กœ ์š”์ฒญ B๋ฅผ ์œ„ํ•œ ํ† ํฐ์„ ์žฌ๋ฐœ๊ธ‰ํ•˜๋ ค๋ฉด ์ด๋ฏธ ์‚ฌ์šฉ๋œ refresh token์œผ๋กœ ํ† ํฐ ์žฌ๋ฐœ๊ธ‰์ด ๋˜์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋Ÿผ ๋‹ค์‹œ 401.. infinite loop์— ๋น ์ง€๊ฒŒ ๋œ๋‹ค.

๊ทธ๋ž˜์„œ isRefreshing์ด true์ด๋ฉด ๊ทธ๋•Œ ์ƒ์„ฑ๋œ ๋‹ค๋ฅธ ์š”์ฒญ๋“ค์€ ์žฌ๋ฐœ๊ธ‰์ด ์™„๋ฃŒ๋˜๋ฉด ๊ทธ ํ† ํฐ๊ณผ ํ•จ๊ป˜ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก failedRequests array์— Promise๋กœ ๋„ฃ์–ด์ฃผ์–ด ํ•ด๊ฒฐํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค.

์ด ๋ถ€๋ถ„์ด ๋‚˜๋Š” ์ข€ ์–ด๋ ค์› ์—ˆ๋Š”๋ฐ, Promise์™€ ๋ฐฐ์—ด ๊ทธ๋ฆฌ๊ณ  ์™ธ๋ถ€ ๋ณ€์ˆ˜๋ฅผ ์–ด๋–ป๊ฒŒ ์กฐํ•ฉํ•ด์•ผํ•˜๋Š”์ง€๊ฐ€ ์ƒ์†Œํ–ˆ๋‹ค.

์ •๋‹ต์€failedRequests์— Promise ๊ทธ ์ž์ฒด๋ฅผ ๋„ฃ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, token(์™ธ๋ถ€ ๋ณ€์ˆ˜)์„ ๋ฐ›์•„ Promise๋ฅผ resolveํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋„ฃ๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.
์ •๋‹ต์„ ์ฐพ๊ธฐ ์ „๊นŒ์ง„ ์˜ ์ดํ•ด๊ฐ€ ์•ˆ๋˜๋Š” ๊ฒƒ ํˆฌ์„ฑ์ด์—ˆ๋Š”๋ฐ,,, ์ฐพ๊ณ  ๋‚˜๋‹ˆ ์ด์ œ์•ผ Promise๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ฒŒ ๋๊ตฌ๋‚˜ ์‹ถ์–ด ๋ฟŒ๋“ฏํ–ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ทธ Promise๋ฅผ return ํ•ด ์–˜๊ฐ€ failedRequests.forEach((f) => f(accessToken))๋ฅผ ํ†ตํ•ด resolve๋  ๋•Œ apiClient.request(originlConfig)๋กœ ์‹คํŒจํ–ˆ๋˜ ์š”์ฒญ์„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋„๋ก ํ•ด์•ผ ํ–ˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ Axios์˜ interceptors.response ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋Š” ์š”์ฒญ์— ๋Œ€ํ•œ ์‘๋‹ต์„ ์ฒ˜๋ฆฌํ•˜๊ณ  Promise๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ด Promise๋Š” ์‘๋‹ต์„ ์™„๋ฃŒํ•˜๊ณ  ํ•ด๋‹น ์‘๋‹ต ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜, ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ๋‹ค๋ฅธ Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. ์—ฌ๊ธฐ์„œ retryOriginalRequest๋Š” ์ด์ „์— ์‹คํŒจํ•œ ์š”์ฒญ์„ ์žฌ์‹œ๋„ํ•˜๊ธฐ ์œ„ํ•œ Promise์ด๋ฉฐ ์ด๋ฅผ ๋ฐ˜ํ™˜ํ•จ์œผ๋กœ์จ, ํ˜„์žฌ ์‹คํŒจํ•œ ์š”์ฒญ์„ Axios์—๊ฒŒ โ€œ๋‹ค์‹œ ์‹œ๋„ํ•ด์•ผ ํ•œ๋‹คโ€๊ณ  ์•Œ๋ ค์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

5. Fetch ๋ง๊ณ  Axios ์‚ฌ์šฉํ•˜๊ธฐ

์œ„์—์„œ ๊ตฌํ˜„ํ•œ ๊ฑธ ํ† ๋Œ€๋กœ apiClient๋ฅผ ์‚ฌ์šฉํ•ด ์š”์ฒญ์„ ๋ณด๋‚ด์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

const getData = () => {
  fetch(`${BASE_URL}/data`, {
    method: "GET",
    headers: {
      authorizationToken: `Bearer ${accessToken}`,
    },
  }).then((resp) => {
    if (resp.ok) {
      resp.json().then((json) => {
        setData(json.data)
    }
  })
}

์ด๋žฌ๋˜ ์š”์ฒญ์„

const getData = () => {
  apiClient.get(`/data`).then((resp) => {
    if (resp.status === 200) {
      setData(resp.data)
  })
}

์ด๋ ‡๊ฒŒ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค. ๋”ฑ ๋ด๋„ ์•„์ฃผ ๊ฐ„๋‹จํ•ด์กŒ๋‹ค.

  • JSON ์ž๋™ ํŒŒ์‹ฑ์œผ๋กœ resp.json().then((json)=>)์˜ ๊ณผ์ •์ด ํ•„์š”์—†์–ด์กŒ์œผ๋ฉฐ,
  • method: โ€œGETโ€์ด ์•„๋‹Œ apiClient.get()์œผ๋กœ get์ž„์„ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ๋‹ค.
  • baseURL๊ณผ token์„ ์ด์ œ๋Š” authProvider์—์„œ ๊ด€๋ฆฌํ•˜๋ฏ€๋กœ ์ปดํฌ๋„ŒํŠธ๋งˆ๋‹ค BASE_URL์„ ๊ฐ€์ ธ์˜ค๊ณ , accessToken์„ ๊ฐ€์ ธ์˜ฌ ํ•„์š”๊ฐ€ ์—†์–ด์กŒ๋‹ค.

์ด๋ฒˆ์—” PUT ์š”์ฒญ์„ ํ•œ ๋ฒˆ ์‚ดํŽด๋ณด์ž.

const updateData = (data) => {
  fetch(`${BASE_URL}/data`, {
    method: "PUT",
    headers: {
      authorizationToken: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({
      data: data,
    }),
  })
}
const updateData = (data) => {
  apiClient.put(`/data`, {
    data: data,
  })
}
  • body ๋ณด๋‚ผ ๋•Œ JSON.stringify()๋„ ํ•„์š”์—†์–ด์กŒ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•œ ๊ณณ์—์„œ ์ด๊ฒƒ์ €๊ฒƒ ๊ด€๋ฆฌํ•˜๊ณ  ๊ฐ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์š”์ฒญ๋ณด๋‚ด๋Š”, fetch๋ฅผ axios๋กœ ๋Œ€์ฒดํ•˜๊ธฐ ๋ !


ยฉ 2023. All rights reserved.

Powered by Hydejack v9.1.6