프론트엔드/JavaScript, TypeScript

동시 여러 개의 토큰 리프레시 요청 관리하기

정현우12 2024. 7. 28. 18:49

현재 프로젝트에서 JWT 인증 방식을 사용 중이다.

api는 공통 클라이언트를 둬서 관리하고, api 응답코드가 401일 경우 토큰 재발급 요청을 보낸다.

한 페이지에서 여러 개의 api 요청을 날리는 경우에, 토큰 재발급 요청이 여러개 날아가면서 토큰이 꼬이게 되는 문제가 있었다.

이 문제를 api 공통 클라이언트가 1개의 토큰 재발급 요청 프로미스를 공유하도록 해서 해결했다.

기존 코드

동시에 여러번 재발급 요청...

// api 공통 클라이언트
if ([401].includes(response.status)) {
    const accessToken = await reissueToken()
    // 후처리
}

// 토큰 재발급
asnyc function reissueToken() {
  // 생략
  return fetch('/reissue')
}

변경 코드

동시 1번만 재발급 요청

// api 공통 클라이언트
if ([401].includes(response.status)) {
    const accessToken = await reissueToken()
    // 후처리
}

// 토큰 재발급
let reissueTokenPromise = Promise<string> | null

asnyc function reissueToken() {
  if(!reissueTokenPromise) {
    reissueToeknPromise = fetch('/reissue')
      .then(res => {
        reissueTokenPromise = null
        return res.token
    })     
  }
  // 생략
  return reissueTokenPromise
}

리팩토링

현재 코드의 문제점은 동시 1번만 요청하도록 하는 로직을 다른 함수에 적용하고 싶을 경우 중복된 코드를 또 써야 한다는 것이다.

토스 slash에 batchRequestsOf 함수를 쓰면 동시 1번만 요청하도록 하는 로직을 따로 함수로 빼서 쓸 수 있다. 

기존 로직에 추가로 함수 args 별로 메모이제이션도 적용할 수 있다.

/** @tossdocs-ignore */
import { noop } from './noop';

export function batchRequestsOf<F extends (...args: any[]) => any>(func: F) {
  const promiseByKey = new Map<string, Promise<ReturnType<F>>>();

  return function (...args: Parameters<F>) {
    const key = JSON.stringify(args);

    if (promiseByKey.has(key)) {
      return promiseByKey.get(key)!;
    } else {
      const promise = func(...args);
      promise.then(() => {
        promiseByKey.delete(key);
      }, noop);
      promiseByKey.set(key, promise);

      return promise;
    }
  } as F;
}