LogoSEO Jing
  • All Posts
  • SEO Jing
  • KD Team
  • CLab CoreTeam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

프론트엔드스터디JavaScript클로저Promise비동기

프론트엔드 스터디 대면 9주차: 클로저, Promise, async/await

2026년 5월 16일·11분 읽기

1. 저번 주 복습

지난주에는 const와 불변성의 차이, 프로토타입 기반 상속, 런타임 타입 체크를 배웠습니다. 핵심만 빠르게 짚어보겠습니다.

불변성(Immutability)

  • const: 변수 바인딩(참조)만 고정 — 객체 내부 변경은 자유롭게 가능
  • Object.freeze(): 최상위 프로퍼티만 동결 (중첩 객체는 여전히 변경 가능)
  • 불변 패턴: 상태를 직접 변경하지 않고 새 객체를 반환 — React useState의 방식

프로토타입

  • 프로토타입 체인: 프로퍼티 탐색 시 자신 → [[Prototype]] → Object.prototype 순서
  • prototype vs [[Prototype]]: 함수만 prototype 프로퍼티를 가짐. new로 만든 인스턴스의 [[Prototype]]이 됨
  • 메서드 공유: Person.prototype.greet처럼 prototype에 추가하면 모든 인스턴스가 공유 (복사 아님)

타입 체크

  • typeof의 한계: null, 배열, Date 등 모두 'object' 반환
  • 배열 확인: Array.isArray() 사용
  • 정밀 확인: Object.prototype.toString.call(value) — '[object Array]' 등 반환

2. 저번 주 과제 정답 및 풀이

인증 미션 과제

Q1. 아래 코드에서 클로저가 만들어지는 위치를 찾고, makeCounter()를 두 번 호출했을 때 각 카운터가 독립적으로 동작하는 이유를 설명하세요.

js
function makeCounter() {
  let count = 0;
  return function () {
    count++;
    return count;
  };
}

const counterA = makeCounter();
const counterB = makeCounter();

console.log(counterA()); // ?
console.log(counterA()); // ?
console.log 

정답: 1, 2, 1

클로저는 return function() { ... } 부분에서 만들어집니다. makeCounter()를 호출할 때마다 새로운 렉시컬 환경(새로운 count 변수)이 생성됩니다. counterA와 counterB는 각각 별도의 렉시컬 환경을 기억하는 클로저이므로 독립적인 count를 가집니다.


Q2. async function foo() {"{"} return 1; {"}"} 에서 foo()의 반환값 타입과 실제 값을 확인하고, 왜 그런지 설명하세요.

js
async function foo() {
  return 1;
}

const result = foo();
console.log(result);
console.log(result instanceof Promise);

정답: result는 Promise 객체, instanceof Promise는 true

async 함수는 반환값을 항상 Promise로 감쌉니다. return 1을 해도 자동으로 Promise.resolve(1)로 래핑됩니다. 이미 Promise를 반환하면 이중 래핑하지 않습니다. async 함수의 결과를 사용하려면 await를 쓰거나 .then()으로 받아야 합니다.


Q3. Promise.all()로 병렬 실행할 때와 순차적으로 await를 연달아 쓸 때의 실행 시간 차이를 설명하세요. 각각 1초 걸리는 비동기 작업 2개라면 총 시간은 어떻게 다를까요?

정답

  • 순차 await: 약 2초 (1초 + 1초)
  • Promise.all(): 약 1초 (동시 시작, 가장 오래 걸리는 작업만큼만 소요)

const a = await fetchA(); const b = await fetchB();는 fetchA가 완료된 후에야 fetchB를 시작합니다. 작업 간 의존성이 없다면 Promise.all()을 써야 합니다. 단, 하나라도 reject되면 전체가 reject되므로, 실패를 개별 처리해야 하면 Promise.allSettled()를 사용합니다.


Q4. 아래 코드의 출력 순서를 예측하고, 이벤트 루프의 마이크로태스크 큐와 매크로태스크 큐의 우선순위를 사용해 설명하세요.

js
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");

정답: A → D → C → B

동기 코드인 A와 D가 먼저 실행됩니다. 이후 콜 스택이 비면 마이크로태스크 큐(Promise.then)가 먼저 처리되어 C가 출력되고, 그 다음 매크로태스크 큐(setTimeout)에서 B가 출력됩니다. setTimeout의 지연 시간이 0이라도 매크로태스크이므로 마이크로태스크보다 늦게 실행됩니다.


3. 면접 질문

이번 주 범위와 관련된 실제 면접 질문들입니다. 정답을 외우기보다는 자신만의 말로 설명하는 연습을 해보세요.

Q1. 클로저(Closure)란 무엇인가요? 어떤 경우에 활용하나요?

클로저는

함수가 선언된 렉시컬 환경을 기억하여, 외부 함수가 종료된 후에도 그 환경의 변수에 접근할 수 있는 것

입니다. 렉시컬 스코프의 자연스러운 결과물입니다.

js
function makeCounter() {
  let count = 0;
  return function () {
    return ++count;
  };
}

const counter = makeCounter(); // makeCounter 종료 후에도
counter(); // 1 — count 변수를 기억
counter(); // 2
counter(); // 3
주요 활용 사례는 세 가지입니다.
  • 데이터 은닉: 외부에서 직접 접근 불가능한 private 변수 구현
  • 메모이제이션: 이전 계산 결과를 캐싱
  • 함수 팩토리: 설정값을 기억하는 함수 생성 (makeAdder(5) 등)

면접 팁: "React의 useState와 useEffect도 클로저를 기반으로 동작합니다. useEffect 의존성 배열을 빠뜨리면 오래된 클로저(stale closure)를 참조하는 버그가 생기는데, 이것도 클로저의 특성 때문입니다"라고 연결하면 좋습니다.

Q2. 콜백 지옥이란 무엇이고, Promise는 어떻게 해결하나요?

콜백 지옥은 비동기 작업을 순차적으로 처리할 때 콜백이 중첩되어 코드가 오른쪽으로 계속 깊어지는 현상입니다. 가독성이 떨어지고 에러 처리도 어렵습니다.

Promise는 비동기 작업의 결과를 값(객체)으로 표현하여 .then() 체이닝으로 수평적으로 나열할 수 있게 합니다. async/await은 Promise를 기반으로 동기 코드처럼 읽히도록 한 문법적 설탕입니다.

면접 팁: "async/await을 쓴다고 Promise를 몰라도 되는 것은 아닙니다. Promise.all()로 병렬 처리를 하거나, 에러 처리를 정확히 하려면 Promise 동작 원리를 이해해야 합니다"라고 답하면 깊이 있는 인상을 줄 수 있습니다.

Q3. 이벤트 루프(Event Loop)란 무엇인가요?

JS는 단일 스레드로 한 번에 하나의 작업만 실행합니다. 그럼에도 비동기 처리가 가능한 이유는 이벤트 루프 덕분입니다.

  • 콜 스택(Call Stack): 현재 실행 중인 함수들의 스택
  • Web API: setTimeout, fetch 등 브라우저가 처리하는 비동기 작업
  • 태스크 큐(Macro): setTimeout, setInterval 콜백이 대기
  • 마이크로태스크 큐(Micro): Promise .then, queueMicrotask — 항상 태스크 큐보다 먼저 실행

이벤트 루프는 콜 스택이 비면 마이크로태스크 큐 → 태스크 큐 순서로 꺼내 실행합니다.

면접 팁: "Promise.resolve().then()은 setTimeout(fn, 0)보다 먼저 실행됩니다. 마이크로태스크 큐가 태스크 큐보다 우선순위가 높기 때문입니다"라고 예시를 들면 좋습니다.


4. 관련 포스팅

  • 프론트엔드 스터디 8주차 학습 자료: 불변성, 프로토타입, 타입 체크

  • 프론트엔드 스터디 9주차 학습 자료: 클로저, Promise, async/await


5. 다음 주 안내

다음 주에는 실무에서 가장 많이 쓰는 JS 문법들을 집중적으로 다룹니다. 배열 고차함수(map, filter, reduce)와 ES6+ 문법 (구조 분해 할당, 스프레드, 옵셔널 체이닝, 모듈)입니다. React 코드를 읽고 쓸 때 거의 매 줄 등장하는 문법들이라 실무 체감이 가장 빠른 주차입니다.

다음 주 예습

다음 주 학습 자료(week9)의 얄코 강의를 시청하거나, 스킵 진단 문제 10개를 풀어보세요. 다음 주부터 React를 시작하므로, map/filter/구조 분해/스프레드 문법에 익숙해지면 React 코드가 훨씬 편하게 느껴질 것입니다.

포스트 목록

/study/clab-26-1/in-person
파일 12개, 폴더 0개
프론트엔드 스터디 대면 1주차: 프론트엔드 개발자란? 그리고 우리가 배울 것들프론트엔드 스터디 대면 2주차: HTML 마크업과 폼, 그리고 CSS의 시작프론트엔드 스터디 대면 3주차: 폼(Form), CSS 선택자, 그리고 박스 모델프론트엔드 스터디 대면 4주차: 박스 모델 실전, Position과 Flexbox프론트엔드 스터디 대면 5주차: 반응형 웹, CSS Grid, 그리고 CSS 마무리프론트엔드 스터디 대면 6주차: JavaScript 시작 — 타입, 변수, 호이스팅프론트엔드 스터디 대면 7주차: 객체, 함수, 스코프프론트엔드 스터디 대면 8주차: 불변성, 프로토타입, 타입 체크프론트엔드 스터디 대면 9주차: 클로저, Promise, async/await프론트엔드 스터디 대면 10주차: React 입문 전 브릿지 — ES6+ 문법과 에러 처리프론트엔드 스터디 대면 11주차: React 첫 번째 — 컴포넌트, JSX, props프론트엔드 스터디 대면 12주차: React 심화 — useState와 useEffect
(
counterB
(
)
)
;
// ?