티스토리 뷰
배경
이전에 면접, 과제 등에서 스택, 큐에 대한 질문을 받은적이 있다. 그때는 실무 경험이 없었기 때문에 막연히 이론적 지식만으로 답변을 했었떤 것 같다. 조금이지만 경험이 쌓인 지금 그 때로 돌아가서 답변해보려고 한다.
실무에서 마주한 문제
js script 최적화
웹에서 js를 사용하려면 script 태그를 사용해야한다. 그리고 async, defer 속성을 통해 실행 순서를 제어할 수 있다.
아래는 과거 기술면접을 준비하면서 적었던 내용이다.
- DOM을 따라 반드시 순서대로 실행되어야 한다면 <script>
- DOM이나 다른 스크립트에 의존성이 없고, 실행 순서가 중요하지 않은 경우라면 <script async>
- DOM이나 다른 스크립트에 의존성이 있고, 실행 순서가 중요한 경우라면 <script defer>
이론적으로는 알고 있었지만, 리액트 개발을 하면서 npm 패키지 방식으로 스크립트를 관리하다 보니 직접 사용할 일이 많지 않았다.
그러던 와중에 나는 다음과 같은 요구사항을 받았다.
- 규모가 있는 Foo 스크립트를 만들고 이를 고객사에 제공해야한다.
- Foo 스크립트는 바로 실행될필요가 없지만 내부의 함수는 바로 호출할 수 있어야 한다.
구현하다보면 다음과 같은 문제를 마주치게 된다.
- script 태그를 <head>에 넣으면 브라우저가 해당 스크립트를 불러오는 동안 DOM 렌더링이 지연된다. 특히 파일이 클수록 지연 시간이 길어진다.
- 그렇다고 async나 defer 속성을 사용하면 고객사에서 스크립트 내부의 함수를 즉시 사용할 수 없다.
이럴 때 큐를 사용하면 해결할 수 있다.
- 고객사에 스크립트 uri만 주는게 아니라 js파일까지 같이준다.
- 함수를 정의하고 이 함수가 실행될 때 인자를 큐에 넣는다.(함수, 큐는 전역 객체로 생성)
- 스크립트는 async를 이용해서 불러온다.
- 스크립트 로딩이 완료되면 큐에 저장된 데이터를 순차적으로 실행한다.
<script>
var fn = function(args) {
queue.push(...args);
}
var queue = [];
var scriptElement = document.createElement('script');
scriptElement.type = 'text/javascript';
scriptElement.async = true;
scriptElement.src = 'cdn/foo.js';
document.body.append(scriptElement);
</script>
이 방식을 사용하면 스크립트가 로드되기 전에 함수를 실행할 수 있고, 로드된 후에는 순차적으로 실행된다.
더 생각해보기
- 전역객체를 두개나 만들었다. 이게 최선일까? 하나로 줄일수는 없을까?
- body에 append하는게 최선일까? 이로 인해 생기는 문제는 없을까?
인증(토큰 재발급 최적화)
과거 나는 우아한형제들 인턴 과정에서 다음과 같은 코드를 사용했다.
async function request<T>(method: Method, url: string, body?: T, multipart?: boolean): Promise<AxiosResponse> {
const headerOption = multipart && { 'Content-Type': 'multipart/form-data' };
try {
const accessToken = window.localStorage.getItem('user') || '';
const res = await client({
method,
url,
headers: {
Authorization: `Bearer ${accessToken}`,
...headerOption,
},
...(body && { data: body }),
});
if ((res.data as IResetToken).requestAgain) {
const { newAccessToken } = res.data as IResetToken;
if (newAccessToken) {
window.localStorage.setItem('user', newAccessToken);
}
const newResult = await request(method, url, body);
return newResult;
}
return res;
} catch (err) {
if (axios.isAxiosError(err)) {
if (err.response && err.response.status === 401) {
window.localStorage.removeItem('user');
window.location.href = '/';
}
}
throw err;
}
지금 보면 여러가지 개선점이 보인다.
- 모든 api에 requestAgain이라는 응답값을 주게 설계했지만 에러를 뱉고 에러 코드를 통해 accessToken을 재발급 받는게 더 일반적일 것 같다.
- axios는 intercept를 이용해서 api 실행전 토큰 주입이나 공용 에러처리가 가능하다.
일단은 인증 관련된 코드에 집중해보자.
당시 accessToken과 refreshToken을 사용했고 accessToken이 만료되면 refreshToken으로 accessToken을 재발급해줬다.
그리고 문제없이 사용했었다.
근데 지금 생각해보니 한가지가 걸린다.
만약 api 5번을 한번에 호출했는데(비동기) 첫번째 api를 호출한 시점에 accessToken이 만료되었으면 어떻게 될까?
불필요하게 4번의 accessToken이 발급될것이다. 이게 최선일까?
토큰 재발급 막기
- accessToken이 만료되었을 때 에러를 뱉고 에러 코드를 통해 accessToken을 재발급 받는 코드로 변경하자.
- api 5개가 순차적으로 실행되고 첫번째 api를 호출한 시점에 accessToken이 만료되었다고 가정하자.
- 이때 accessToken이 만료되었다는 에러코드를 보자마자 큐에 함수를 넣고 status를 pending으로 변경한다. 그리고 status가 pending이라면 api를 실행하지 않고 큐에 담는다.
- accessToken 재발급에 성공하면 status를 다시 되돌린다.
- 이렇게하면 불필요한 토큰재발급없이 문제를 해결할 수 있다.
예상질문
인증이 필요한 api와 필요하지 않은 api가 섞여있으면 어떻게하나요?
- axios instance를 인증이 필요한 경우와 필요하지 않은 경우를 나누면 됩니다.
firebase 토큰 재발급
firebase를 자주 사용하면서 "Firebase는 토큰 재발급을 어떤 방식으로 처리할까?" 하는 궁금증이 생겨 직접 테스트해봤다.
firebase에서 인증 정보를 백엔드에 전달하려면 `getIdToken()`을 호출해서 accessToken을 받아야 한다. 이 함수를 테스트해본 결과, 만약 토큰이 만료되었다면 내부적으로 자동으로 accessToken 재발급 API를 호출한 후, 새로 발급된 토큰을 반환한다.
즉, 클라이언트가 직접 토큰의 만료 시간을 확인하고, 만료된 경우 먼저 재발급을 요청한 뒤 백엔드 API를 호출하는 방식인 것 같다. 이러한 방식은 명시적인 로직 없이도 토큰 유효성을 안전하게 유지할 수 있어 꽤 괜찮아 보인다.
후기
사실 요구사항은 훨씬 복잡했고 위 작업은 문제해결과정 중 하나이다.
기본기를 중요시하는 이유는 이런 것 같다. 경험이 쌓이고 다양한 문제를 해결해야할 때 결국은 기본기가 필요하다. 요즘은 ai가 많이 도와주지만 그래도 아직까지는 기본기가 꽤 중요하지 않나 생각이 든다.
'기술' 카테고리의 다른 글
캐시컨트롤 세부적으로 다루기 1편(기본편) (2) | 2025.03.30 |
---|---|
추상화 > 카테고라이즈 실무에서 적용하기(enum) (0) | 2025.03.19 |
타사 제품 분석해보기(네이버 쇼츠, 유튜브 쇼츠) (1) | 2025.03.11 |
useState, useEffect, useRef 만들기(feat. 클로저) (1) | 2025.02.27 |
비동기 함수 효율적으로 처리하기 (0) | 2025.02.12 |