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

Contact Me

© 2026 SEOJing. All rights reserved.

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

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

2026년 5월 18일·20분 읽기

8주차 학습 자료

  • 입문 강의 (처음이라면): 얄코 JS 입문 강의

    비동기 처리(Promise, async/await) 챕터

  • 강의 스킵 가능: 아래 스킵 진단 문제 10개를 모두 맞히면 강의 시청 스킵 가능합니다.
  • 심화 자료 (딥다이브 대비):

    딥다이브 요약 GitHub

안녕하세요. 8주차입니다. 이번 주는 두 가지 큰 주제를 다룹니다. 클로저는 지난 주에 배운 렉시컬 스코프에서 자연스럽게 이어지는 개념으로, React의 useState 같은 훅이 어떻게 동작하는지를 이해하는 기반입니다. 비동기 처리는 서버에서 데이터를 가져오거나 파일을 읽는 등 시간이 걸리는 작업을 처리하는 JS의 핵심 방법입니다. 콜백에서 Promise, async/await으로 이어지는 발전 흐름을 이해하는 것이 목표입니다.


스킵 진단 문제

아래 10문제를 막힘없이 풀 수 있다면,

이번 주 학습 자료를 생략하거나, 모르는 부분만 선택적으로 읽으셔도 좋습니다.

바로 하단의 더 알면 좋을 것들을 확인해보세요.

Quiz1 / 10
Q.클로저(Closure)란 무엇인가요?

1. 클로저(Closure) — 렉시컬 환경을 기억하는 함수

클로저는 함수가 선언될 때의 렉시컬 환경을 기억하여, 그 환경의 변수에 계속 접근할 수 있는 특성입니다. 렉시컬 스코프의 자연스러운 결과물입니다.

js
function makeCounter() {
  let count = 0; // 외부 함수의 변수

  return function () {
    // 내부 함수 — 클로저
    count++;
    return count;
  };
}

const counter = makeCounter();
// makeCounter()는 이미 종료됐지만
console.log(counter()); // 1
console.log(counter()); // 2
console.log( 

데이터 은닉

클로저를 활용하면 외부에서 직접 접근할 수 없는 private 변수를 만들 수 있습니다.

js
function createAccount(initialBalance) {
  let balance = initialBalance; // 외부에서 직접 접근 불가

  return {
    deposit(amount) {
      balance += amount;
    },
    withdraw(amount) {
      if (amount > balance) return "잔액 부족";
      balance -= amount;
    },
    getBalance() {
      return balance;
    },
  };
}

const account = createAccount

React와 클로저

React의 useState 훅이 클로저를 기반으로 동작합니다. 컴포넌트가 리렌더링될 때마다 이벤트 핸들러 내부의 count, setState 등이 클로저로 기억됩니다. useEffect의 의존성 배열을 빠뜨리면 오래된 클로저(stale closure)를 참조하는 버그가 생기는 이유도 클로저 때문입니다.


2. Promise — 콜백 지옥 탈출

서버에서 데이터를 가져오는 fetch, 타이머 setTimeout 등은 결과가 나중에 옵니다. 과거에는 콜백으로 처리했고, 중첩이 깊어지면 콜백 지옥 이 생겼습니다. Promise는 비동기 작업의 결과를 값(객체)으로 표현하여 이를 해결합니다.

js
// 콜백 지옥
fetch("/user", function (user) {
  fetch("/posts?userId=" + user.id, function (posts) {
    fetch("/comments?postId=" + posts[0].id, function (comments) {
      // 점점 깊어짐...
    });
  });
});

// Promise 체이닝 — 수평으로 나열
fetch("/user")
  .then((user)    user

Promise 상태

  • pending: 비동기 작업 진행 중 (초기 상태)
  • fulfilled: 작업 성공 — .then() 콜백 실행
  • rejected: 작업 실패 — .catch() 콜백 실행
  • 한 번 fulfilled/rejected 상태가 되면 변경 불가

여러 Promise 동시 처리

js
// 순차 실행 — 느림 (앞이 끝나야 다음 시작)
const a = await fetchA();
const b = await fetchB();

// 병렬 실행 — 동시에 시작하고 모두 완료 기다림
const [a, b] = await Promise.all([fetchA(), fetchB()]);

// 가장 먼저 완료된 것만 사용
const fastest = await Promise.race([fetchA(), fetchB()]);

3. async/await — Promise를 동기처럼

async/await은 Promise를 기반으로 한 문법적 설탕(syntactic sugar)입니다. 비동기 코드를 마치 동기 코드처럼 읽기 쉽게 작성할 수 있게 해줍니다.

js
// Promise 체이닝
function getUser(id) {
  return fetch(`/users/${id}`)
    .then((res) => res.json())
    .then((user) => user)
    .catch((err) => {
      throw err;
    });
}

// async/await — 같은 동작, 더 직관적
async function  

주의: await 병렬 실행

js
// 잘못된 예 — 순차 실행 (불필요하게 느림)
async function bad() {
  const a = await fetchA(); // fetchA 완료 대기
  const b = await fetchB(); // 그 다음에야 fetchB 시작
}

// 올바른 예 — 병렬 실행
async function good() {
  const [a, b] = await Promise.all([fetchA(), fetchB()]);
}

실무에서는 React 컴포넌트에서 데이터를 가져올 때 useEffect 안에서 async 함수를 정의하고 호출하는 패턴을 주로 사용합니다.

js
useEffect(() => {
  async function fetchData() {
    try {
      const res = await fetch("/api/data");
      const data = await res.json();
      setData(data);
    } catch (err) {
      setError(err);
    }
  }

  fetchData();
}, []);

📝 학습 이해 퀴즈

아래 코드를 브라우저 콘솔에서 직접 실행해보고 결과를 입력해보세요.

Quiz1 / 3
Q.아래 코드의 출력 결과를 순서대로 입력하세요.
js
function makeAdder(x) {
return function (y) {
  return x + y;
};
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

console.log(add5(3));
console.log(add10(3));
console.log(add5(add10(1)));

📝 8주차 스터디 인증 미션

아래 질문 중 하나를 직접 찾아보고 답을 댓글로 남겨주세요.
Quiz1 / 4
Q.아래 코드에서 클로저가 만들어지는 위치를 찾고, 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(counterB()); // ?

관련 포스팅

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

포스트 목록

/study/clab-26-1
파일 13개, 폴더 2개
프론트엔드 스터디 1주차: 마크업 그 이상, 실무를 위한 중급 HTML 가이드프론트엔드 스터디 2주차: 사용자와 소통하는 폼 & CSS의 시작프론트엔드 스터디 3주차: 프론트엔드의 첫 번째 벽, 박스 모델과 스타일링프론트엔드 스터디 4주차: 자유자재 레이아웃 (포지션과 플렉스박스)프론트엔드 스터디 5주차: JavaScript 시작 — 타입, 변수, 그리고 JS가 이상한 이유프론트엔드 스터디 6주차: 객체, 함수, 그리고 스코프프론트엔드 스터디 7주차: 불변성, 프로토타입, 타입 체크프론트엔드 스터디 8주차: 클로저, Promise, async/await프론트엔드 스터디 9주차: React 입문 전 필수 JS 문법 — map, 구조 분해, 스프레드프론트엔드 스터디 10주차: React 기초 1 — 컴포넌트, JSX, props, useState프론트엔드 스터디 11주차: React 기초 2 — useEffect, 이벤트 처리, 폼프론트엔드 스터디 심화: this, 실행 컨텍스트, 이터러블프론트엔드 스터디 심화: 에러 처리와 정규 표현식
counter
(
)
)
;
// 3
// count 변수가 여전히 살아있음
(
1000
)
;
account.deposit(500);
console.log(account.getBalance()); // 1500
console.log(account.balance); // undefined — 직접 접근 불가
=>
fetch
(
"/posts?userId="
+
.
id
)
)
.then((posts) => fetch("/comments?postId=" + posts[0].id))
.then((comments) => console.log(comments))
.catch((err) => console.error(err));
getUser
(
id
)
{
try {
const res = await fetch(`/users/${id}`);
const user = await res.json();
return user;
} catch (err) {
throw err;
}
}