오늘의 면접 주제는
🧩 Q1. 자바스크립트에서 비동기 처리를 위한 세 가지 방식인 콜백, 프로미스, async/await 각각에 대해 간단히 설명해주시고, 이들이 등장한 이유와 장단점을 설명해주세요.
내 답변 : 콜백함수는 인자로 함수를 받아서 비동기 작업을 마치면 다음 작업을 할 수 있도록 하는 비동기 처리방법이고 비동기 순서를 보장할 수 있다는 장점이 있으나 함수로 인자를 넘기는 것을 과도하게 사용하게 될 경우 콜백지옥이 발생하여 가독성이나 유지보수 측면에서 좋지 않게 됩니다.
프로미스는 콜백지옥을 방지하고자 나오게 된 비동기 처리 방법으로 작업이 정상적으로 끝났을 경우 resolve, 에러처리를 위한 reject로 나누어 받게 되며, 작업이 끝난뒤 비동기 순서를 then 메소드 체이닝 방법을 통해 처리하게 됩니다. 이 과정에서의 에러처리는 catch를 통해서 해결 할 수 있게 됩니다. 콜백지옥을 해결할 수는 있지만 여전히 과도한 메소드 체이닝 방법을 통한것은 가독성적으로 좋지 않다는 단점이 존재하게 됩니다.
async/await는 위 방법들의 단점을 해결할 수 있는 문법적 설탕, 비동기 처리 방식으로 try/catch문을 사용하여 비동기 작업을 편리하게 사용할 수 있도록 합니다. 단점으로는 병행처리가 필요한 로직부분에서 직접적으로 병행처리를 할 수 없다는 점과 여러 비동기 처리가 필요한 부분의 로직에서 별도의 에러처리를 할 수 없다는 것이 단점으로 뽑을 수 있게 됩니다.
이들이 등장하게 된 이유는 api와 같은 처리량이 큰 작업들은 우선 진행 후 작업이 필요한 부분들을 동기적으로 실행되어 값을 받아온 뒤 진행이 되어야 하는데 싱글 스레드의 작업상 동기적인 처리를 필요로 하여 에러를 방지하고 정상적인 코드 흐름을 갖을 수 있도록 해당 처리 방안들이 고안되어 탄생하게 되었다.
피드백
의도는 좋습니다. 각 방식의 등장 배경과 장단점, 흐름을 잡으려고 한 점은 칭찬할만합니다. 근데 문장의 구조가 너무 길고 모호합니다. "있게 됩니다.", "하게 됩니다."같은 말투는 기술 면접에서 피해야 할 대표적인 말투이거든요. 너무 모호하고 책임 회피로 보일 수 있어요. 정확성에서 오해의 여지가 있는데, 예를들어 프로미스는 단순한 체이닝에 너무 집중되어 있어요. 상태 기반의 비동기 추상화 라는 본질적인 목적도 있거든요. async/await의 단점도 약간 애매한데.. 별도의 에러를 처리할 수 없다. 라는 말은 물론 여러 await부분들에서 별도의 에러를 처리할 수 없다. 이점을 들고 싶었겠죠? 근데 try/catch문을 잘못 이해한것과 같은 느낌을 줄 수 있어요.
각각 항목에 대한 피드백
- 콜백
장점으로 "비동기 순서를 보장할 수 있다는 장점"이라고 했는데, 콜백을 순서를 보장하는게 아니라, 특정 시점에 실행을 보장하는 것입니다. 예를 들면 ajax요청이 끝난 후 그 다음 동작을 수행하는걸 "콜백"으로 연결했을 뿐이지, 전체적인 흐름의 순서를 제어하기엔 한계가 있었죠.- 프로미스
resolve, reject로 나뉜다..? 정확히는 프로미스는 세가지 상태 (pending, fulfilled, rejected)를 가진 객체입니다.
"콜백 지옥을 해결했다"는 건 맞아요. 그보다 더 본질은 에러 전파, 명확한 상태관리, 체이닝을 통한 예측 가능한 흐름을 위한 겁니다.
"과도한 체이닝은 가독성이 떨어진다"는 주장도 일부 동의는 합니다만. 그게 단점의 핵심이 아니거든요. 상황에따라 다른거니까- async/await
"병행처리를 직접 할 수 없다"는 틀려요 사실 Promise.all()를 사용해서 병렬처리는 가능하잖아요? await를 사용하면 직렬/병렬 조절이 가능하니깐요
"별도의 에러처리를 할 수 없다"도 틀립니다. try/catch를 여러번 나누어 쓰거나, 함수 단위로 분리해서 로컬 에러 핸들링도 가능합니다.
모범 답안 예시
자바스크립트는 싱글 스레드 기반이기 때문에, 시간이 오래 걸리는 작업을 동기적으로 처리하면 전체 흐름이 멈추는 문제가 생깁니다. 그래서 이런 작업을 비동기로 처리할 수 있는 다양한 방식이 발전해왔는데요.
가장 먼저 등장한건 콜백함수입니다. 비동기 작업이 끝났을때 실행할 함수를 인자로 넘기는 방식인데요 간단하지만 콜백안에 또 콜백이 중첩되다 보면 '콜백 지옥'이라는 문제가 생깁니다. 가독성이 떨이조 에러 처리도 어렵다는 단점이 있습니다.
이런 문제를 해결하기 위해 등장한게 "프로미스"입니다. 비동기 작업의 상태를 pending, fulfilled, rejected로 관리하면서 .then(), .catch() 체이닝을 통해 흐름을 구성할 수 있어, 콜백 지옥을 완화시킬 수 있었습니다. 하지만 체이닝이 길어지면 여전히 가독성이 떨어질 수 있고, 복잡한 로직에서는 다소 번잡해질 수 있습니다.
마지막으로 나온 방식이 async/await인데요, 프로미스를 기반으로 한 문법적인 설탕으로, 비동기 코드를 마치 동기 코드처럼 작성할 수 있어 가독성이 크게 향상됩니다. 에러 처리는 try/catch로 명확하게 할 수 있고요. 다만 병렬처리가 필요한 경우에는 Promise.all()과 같은 방식과 함께 사용해야하며, 내부적으로는 여전히 프로미스 기반이기 때문에 기본개념에 대한 이해는 필요합니다.
정리하자면, 콜백은 구조가 단순하지만 중첩문제 인해 유지보수가 어렵고, 프로미스는 상태 기반으로 콜백의 단점을 어느정도 보완했지만 체이닝의 한계가 있고, async/awaits는 가장 읽기 쉬운 구조지만 병렬처리 시 주의가 필요합니다. 이 세가지 방식은 자바스크립트 환경에서 비동기 작업을 점점 더 안정적의고 가돗어 좋게 처리하기 위해 진화해 온 흐름이라고 생각합니다.
브라우저 저장소(localStorage, sessionStorage, cookie) 비교 문제
🧩 localStorage, sessionStorage, cookie 각각의 특징과 차이점을 말해보시겠어요?
내 답변 : 로컬 스토리지는 창을 닫게 되더라도 데이터가 유지가 된다는 특징을 갖고 있고 세션 스토리지는 창마다 각각의 데이터 스토리지를 갖고 있다는 특징이 있지만 사용자가 창을 종료를 하게 된다면 데이터가 소실될 수 있는 스토리지입니다. 쿠키는 서버와 클라이언트 간 세션을 주고받을 때 사용하는 스토리지로 서버 헤더에 포함되어 있어 httponly와 같은 특징을 갖고 있어 xss에 대해서 보안적인 문제를 대비할 수 있어 인증/인가와 같은 로직에서 많이 사용되는 스토리지입니다. 용량적인면에서도 앞서 로컬 스토리지와 세션 스토리지는 각각 5~10md가량의 용량을 사용하고 있지만, 쿠키는 4Kb의 용량을 사용하고 있습니다.
피드백 : "세션스토리지는 창마다 스토리지를 갖고 있다" -> 정확히는 탭(이나 윈도우 세션) 마다로 말씀하시는게 정확합니다. "쿠키는 서버와 클라이언트 간 세션을 주고받는 스토리지" -> 조금 모호합니다. 자동 전송, 서버 설정 가능, 보안 옵션들 등 핵심으로 말해줘야 합니다. 그리고 문장이 너무 길어서 핵심이 잘 안들립니다. 면접에서는 짧고 명확하게 나눠 말하는게 좋습니다.
답 : localStorage, sessionStorage, cookie는 모두 브라우저에서 데이터를 저장하기 위한 수단이지만, 저장 위치, 수명, 접근 방식 용도가 다릅니다.
먼저 localStorage는 브라우저에 데이터를 영구적으로 저장하며, 사용자가 직접 삭제하지 않는 이상 브라우저를 닫아도 데이터가 유지 됩니다.
반면에 sessionStorage는 브라우저 탭 단위로 데이터를 저장하고 탭이나 브라우저를 닫으면 데이터가 사라집니다. 그래서 같은 도메인이라도 탭마다 별개의 저장소를 갖는 특징이 있습니다.
그리고 cookie는 브라우저와 서버간에 자동으로 전송되는 작은 데이터 조각입니다. 주로 인증 정보나 세션 ID처럼 서버와의 통신에 필요한 정보를 저장하는데 사용됩니다. 쿠키는 max-age나 expires 옵션으로 수명 조절이 가능하고 HttpOnly, Secure 등의 보안 옵션을 통해 XSS나 네트워크 공격에 대비할 수 있습니다.
또 하나의 중요한 차이점은 용량 제한인데요 localStorage와 sessionStorage는 보통 약 5~10MB까지 저장할 수 있지만, 쿠키는 4KB 내외로 제한적입니다. 그만큼 쿠키는 가볍고 빠르게 서버에 전달해야 할 정도에 주로 사용됩니다.
🧩 localStorage와 sessionStorage 중에서, 브라우저 창을 닫아도 데이터가 사라지지 않는 건 어떤 스토리지 인가요? 이유도 간단히 말해주세요.
내 답변 : 로컬 스토리지 입니다. 로컬 스토리지는 클라이언트 브라우저 단에서 영구적 저장이 되는 특징을 갖고 있어 사용자가 직접 데이터를 삭제하거나 쿠키 만료 전까지 계속 유지가 된다는 점이 있기 때문입니다.
답 : 로컬 소토리지입니다. 로컬 스토리지는 클라이언트의 브라우저 영구적으로 저장되도록 설계되어 있어서, 브라우저를 닫거나 컴퓨터를 재시작하더라도 데이터가 유지 됩니다. 반면에 세션스토리지는 탭이 닫히면 함께 삭제되는 일시적인 저장소 입니다.
🧩 쿠키(cookie)가 로컬스토리지, 세션스토리지와 달리 매 요청마다 서버에 자동으로 전송되는 이유는 뭐라고 생각하시나요?
내 답변 : 쿠키는 로컬스토리지와 세션스토리지와 달리 브라우저에만 저장이 되는것이 아닌 서버와 클리아언트 간 요청을 주고 받는 스토리지입니다. 서버 헤더에 포함되어 가장 큰 특징인 httponly라는 특징을 갖고 있는데요 이는 자바스크립트 코드로 접근이 불가능하고 서버에서만 접근이 가능하기 때문입니다.
답 : 쿠키는 클라이언트에 저장되지만, 요청이 발생할 때마다 브라우저가 자동으로 쿠키를 HTTP 헤더에 포함시켜 전송하기 때문입니다. 이는 쿠키가 원래 HTTP 통신에 맞춰 설계된 저장 방식이고, domain, path, secure, HttpOnly 같은 옵션을 통해 서버가 원하는 범위와 방식으로 활요할 수 잇게 되어 있어서 그렇습니다.
🧩 HttpOnly 옵션이 붙은 쿠키는 자바스크립트에서 접근할 수 없다고 했는데, 이게 왜 보안에 좋을까요? 간단하게 설명해주세요
내 답변 : 자바스크립트로 접근할 수 있게 된다면 크로스사이트스크립팅 라고 하는 보안적인 이슈가 생기기 때문입니다. 그 말은 즉슨 자바스크립트로 임의 조작이 가능하다는 뜻인데 이는 HttpOnly옵션이 붙게 되면서 직접적인 접근을 막을 수 있어 보안적인 면에서 이점이 될 수 있기 때문입니다.
답 : HttpOnly 옵션이 설정된 쿠키는 자바스크립트에서 읽을 수 없기 떄문에, 만약 웹 페이지에 크로스사이트스크립팅 공격이 발생하더라도 악성 스크립트가 쿠키에 접근할 수 없습니다. 이런 방식으로 민감한 인증 정보를 보호할 수 있기 때문에 특히 로그인 토큰이나 세션 ID같은 정보에는 반드시 HttpOnly 설정을 권장합니다.
React 상태 관리의 핵심: useState, useReducer, 그리고 상태 흐름
🧩 useState와 useReducer의 차이점이 무엇인가요? 그리고 각각 어떤 상황에서 사용하는 것이 적절한지 설명해주세요.
내 답변 : useState는 간단한 상태 변경이 필요한 부분에서 사용하는것이 적합합니다. button을 클릭여부를 판별할때 와 같은 가벼운 상태 관리를 관리할때 사용하는것이 적절하며, useReducer는 action을 받아 다양한 상태를 반환하는 것이 특징입니다.
피드백 : 간단한 상태? 맞아요. 근데 어떤 상태가 간단한거죠? 구체적인 예시가 들어가야합니다. 예를 들면 토글 버튼의 on/off, 입력폼의 텍스트 상태, 또는 숫자를 올리는 카운트값처럼 하나의 값만 있고, 상태 변화가 단순한 경우를 뜻합니다. 이런 경우에는 useState로 처리하는게 간단하고 직관적이기 때문입니다.라고 하는게 좋겠네요. useReducer부분도 너무 간단해요. 액션 받아 다양한 상태 반환이 맞긴 한데 핵심 로직 분리, 복잡한 상태 전이에 적합하다는 점을 뽑을 수 있죠. 디스패치 라는 용어를 안쓴것도 조금 아쉽네요.
🧩 React에서 상태가 변경될 때 컴포넌트가 다시 렌더링되는 이유와 그 흐름을 간단히 설명해주세요.
내 답변 : 리액트는 단일 데이터 흐름을 갖고 있는데요. 상태가 바뀌면 그 상태를 가진 컴포넌트 부터 리렌더링을 하게 됩니다. 부모 컴포넌트가 변경이 되면 자식 컴포넌트도 같이 리렌더링을 하게 되는데 이에 따른 상태 관리도 중요하게 됩니다.
피드백 : 단일 데이터 흐름 언급은 좋은데요. 그건 props흐름에 가까운 말인거고, 여기서는 상태 -> 렌더링 -> 가상 DOM -> 리액트 비교 -> 업데이트 순을 간단히 말하는게 좋습니다. "부모가 변경되면 자식도 리렌더링 된다" 괜찮기는 한데 자식 상태나 props가 바뀌지 않으면 리렌더링 안되는 경우도 있다는 걸 알고 있단 느낌을 줘야 깊이 있게 보일 수 있어요.
🧩 부모 컴포넌트에서 자식 컴포넌트로 상태를 전달하는 방법과, 자식 컴포넌트가 부모 상태를 변경하는 방법에 대해 말해보세요.
내 답변 : 부모에서 자식으로는 props를 통해서 상태를 전달하게 됩니다. 과도한 props를 통한 상태관리는 props drilling을 유발할 수 있으니 유의해야합니다. 반대로 자식 컴포넌트가 부모 상태를 변경하기 위해서는 단일 데이터 흐름을 갖고 있는 리액트의 특성 상 이벤트 함수 콜백을 올려줘서 상태 변경을 요청 해야합니다.
피드백 : props로 전달한다는 건 좋은데요. props drilling까지도 좋습니다. 다만 이벤트 콜백을 올려준다는 것은 말은 되지만 콜백 함수를 props로 전달한다는 정확한 용어로 바꾸는게 좋습니다. 단방향 데이터 흐름 언급은 적절했습니다.