🔄 07. 읎벀튞 룚프 — JS가 멈추지 않는 읎유, 비동Ʞ가 작동하는 진짜 원늬

2026년 3월 8음 수정됚

📋 개요

윜 슀택, 태슀크 큐, 마읎크로태슀크 큐의 ꎀ계, 렌더링 타읎밍, setTimeout(0)의 진짜 의믞륌 싀묎 ꎀ점윌로 완전 정복합니닀.

🎯 읎 섹션을 읜고 나멎:

  • 윜 슀택, 웹 API, 태슀크 큐, 마읎크로태슀크 큐의 ꎀ계륌 귞늌윌로 섀명할 수 있닀.
  • setTimeout(fn, 0)읎 왜 슉시 싀행되지 않는지 읎유륌 안닀.
  • Promise .then읎 setTimeout볎닀 뚌저 싀행되는 읎유륌 섀명할 수 있닀.

📋 목찚


📌 읎 묞서륌 읜Ʞ 전에

⏱ 예상 읜Ʞ 시간: 18분(전첎) / 핵심 파튾만: 11분

🗺 읎 묞서의 흐멄
[닚음 슀레드의 현싀] → [윜슀택 + 큐 구조] → [마읎크로 vs 맀크로 태슀크] → [싀묎 적용]

🎯 읎 묞서륌 ë‹€ 읜윌멎 할 수 있는 것

  • setTimeout(fn, 0) 윜백읎 Promise .then볎닀 나쀑에 싀행되는 읎유륌 섀명한닀.
  • ꞎ 동Ʞ 작업읎 UI륌 멈추게 하는 읎유와 핎결책을 안닀.
  • queueMicrotask륌 ì–žì œ 쓰는지 읎핎한닀.

🗺 읎 묞서의 배겜 섞계ꎀ: '영수넀 컀뮀니티'

🐣 영철: "영혞 님, 읎상한 버귞가 있얎요. 검색 결곌 5천 개륌 DOM에 한 번에 렌더링하는 윔드륌 싀행했더니... 람띌우저가 완전히 얌얎버렞얎요! 슀크례도 안 되고, 큮멭도 안 되고, 몇 쎈 지나서알 결곌가 뿌렀지는데 — 읎 동안 유저가 아묎것도 못 핎요. JavaScript가 싱Ꞁ 슀레드띌서 귞런 걎가요?"

🊁 영혞: "정확핎. JS는 싱Ꞁ 슀레드알. 묎거욎 동Ʞ 작업읎 윜 슀택을 찚지하멎 읎벀튞 룚프가 닀륞 음을 처늬 못 핮 — 큎늭, 슀크례, 렌더링 전부 멈춰. 읎게 읎벀튞 룚프의 작동 방식을 몚륎멎 만나는 대표적읞 UX 재앙읎알. 였늘 읎벀튞 룚프 낎부 구조륌 읎핎하멎, 람띌우저륌 멈추지 않는 윔드륌 ì§€ 수 있게 돌."


🀔 1. 왜 알아알 하는가?

영숙 님의 UX 읎슈 제볎가 읎 몚든 공부의 시작읎었닀. "버튌 누륎멎 화멎읎 3쎈 동안 얌얎요." ê·ž 원읞 하나륌 파고듀멎 JS 런타임 전첎가 볎읞닀.

JS는 싱Ꞁ 슀레드 ë‹€. 한 번에 하나의 작업만 싀행할 수 있닀. 귞런데 얎떻게:

  • HTTP 요청을 하멎서도 화멎읎 멈추지 않는가?
  • 타읎뚞가 만료되멎 자동윌로 윜백읎 싀행되는가?
  • 큎늭, 슀크례 읎벀튞가 닀륞 윔드와 동시에 처늬되는가?

읎 몚든 것읎 읎벀튞 룚프(Event Loop) 가 ꎀ늬하는 비동Ʞ 처늬 메컀니슘 덕분읎닀.


🏗 2. JS 런타임 아킀텍처

닚음 슀레드의 비밀

┌─────────────────────────────────────────────────────────────┐
│                     JS 런타임 (람띌우저)                       │
│                                                              │
│  ┌──────────────┐   ┌──────────────────────────────────┐    │
│  │   JS 엔진    │   │           Web APIs               │    │
│  │  (V8 등)     │   │  (setTimeout, fetch, DOM events) │    │
│  │              │   │                                  │    │
│  │  ┌────────┐  │   │  비동Ʞ 작업을 JS 슀레드 밖에서   │    │
│  │  │윜 슀택 │  │   │  처늬 → 완료 시 큐에 윜백 추가   │    │
│  │  └────────┘  │   └──────────────────────────────────┘    │
│  │              │                    │                       │
│  │  ┌────────┐  │   ┌────────────────▌──────────────────┐   │
│  │  │  힙    │  │   │          태슀크 큐 (Macro)         │   │
│  │  │(메몚늬)│  │   │  setTimeout, setInterval, I/O     │   │
│  │  └────────┘  │   └───────────────────────────────────┘   │
│  └──────────────┘                   │                       │
│         ▲                ┌──────────▌──────────────────┐    │
│         │                │      마읎크로태슀크 큐        │    │
│         │                │  Promise.then, queueMicrotask│    │
│         │                └─────────────────────────────┘    │
│         │                           │                       │
│         └──── 읎벀튞 룚프가 쀑재 ────┘                       │
└─────────────────────────────────────────────────────────────┘

윜 슀택 (Call Stack)

핚수가 싀행될 때마닀 찚곡찚곡 쌓읎는 공간입니닀. 영철읎가 게시Ꞁ 2,000개륌 한 번에 화멎에 귞늬렀닀 람띌우저륌 멈추게 했던 상황을 윜 슀택의 ꎀ점에서 읎핎핎 볎겠습니닀.

function a() { b(); }
function b() { c(); }
function c() { console.log("싀행!"); }
 
a();
 
// 윜 슀택 변화:
// [GEC] → [GEC, a] → [GEC, a, b] → [GEC, a, b, c]
// "싀행!" 출력
// [GEC, a, b, c] → [GEC, a, b] → [GEC, a] → [GEC] → []
// 슀택읎 비워지멎 읎벀튞 룚프가 큐륌 확읞

웹 API & 태슀크 큐

setTimeout읎나 fetch 같은 작업은 JS 엔진 밖에서 처늬됩니닀. 작업읎 끝나멎 윜백 핚수듀읎 '쀄'을 서서 Ʞ닀늬는데, 읎곳읎 바로 태슀크 큐입니닀.

console.log("동Ʞ 1");
 
setTimeout(() => {
  console.log("타읎뚞 윜백"); // 태슀크 큐 → 윜 슀택읎 빌 때 싀행
}, 0);
 
console.log("동Ʞ 2");
 
// 출력 순서:
// "동Ʞ 1"  — 윜 슀택에서 슉시
// "동Ʞ 2"  — 윜 슀택에서 슉시
// "타읎뚞 윜백" — 윜 슀택읎 비워진 후 태슀크 큐에서

마읎크로태슀크 큐 (Microtask Queue)

Promise.then, queueMicrotask, MutationObserver는 마읎크로태슀크 큐 에 듀얎간닀. 읎 큐는 태슀크 큐볎닀 우선순위가 높닀.


🔄 3. 읎벀튞 룚프의 동작 순서

싀행 우선순위

읎벀튞 룚프는 정핎진 순서에 따띌 큐륌 확읞합니닀. 특히 마읎크로태슀크 큐는 '특권잵'곌 같아서, 음반 태슀크 큐볎닀 뚌저, 귞늬고 완전히 비워질 때까지 처늬됩니닀.

1. 윜 슀택의 동Ʞ 윔드 싀행
2. 윜 슀택읎 비얎지멎:
   2-1. 마읎크로태슀크 큐 전부 소진 (하나씩 꺌낎 싀행)
        └ 마읎크로태슀크에서 새 마읎크로태슀크 추가돌도 전부 처늬
   2-2. 렌더링 업데읎튞 (필요 시, 람띌우저)
   2-3. 태슀크 큐에서 태슀크 하나 꺌낎 싀행
   2-4. 닀시 2-1부터 반복
// 싀행 순서 예잡 연습
console.log("1⃣ 동Ʞ");
 
setTimeout(() => console.log("4⃣ 태슀크 큐 (setTimeout)"), 0);
 
Promise.resolve()
  .then(() => console.log("3⃣ 마읎크로태슀크 (Promise.then)"))
  .then(() => console.log("3⃣-2 마읎크로태슀크 (두 번짞 then)"));
 
console.log("2⃣ 동Ʞ");
 
// 출력:
// 1⃣ 동Ʞ
// 2⃣ 동Ʞ
// 3⃣ 마읎크로태슀크 (Promise.then)   ← 태슀크 큐볎닀 뚌저!
// 3⃣-2 마읎크로태슀크 (두 번짞 then)  ← 마읎크로태슀크 큐가 빌 때까지
// 4⃣ 태슀크 큐 (setTimeout)          ← 마읎크로태슀크 ë‹€ 끝난 후

렌더링 타읎밍

람띌우저가 화멎을 귞늬는 것도 하나의 '태슀크'입니닀. 윜 슀택읎 바쁘멎 읎 소쀑한 렌더링 작업조찚 뒀로 밀늬게 됩니닀.

// 영수넀 컀뮀니티 — 왜 DOM 업데읎튞가 한 번에 반영되는가?
 
const list = document.getElementById("post-list");
 
// 읎 룚프는 동Ʞ적윌로 싀행되는 동안 DOM을 100번 조작하지만
// 화멎에는 당 한 번만 렌더링된닀
for (let i = 0; i < 100; i++) {
  const item = document.createElement("li");
  item.textContent = `게시Ꞁ ${i}`;
  list.appendChild(item);
}
// 윜 슀택읎 비워진 후, 읎벀튞 룚프가 렌더링 업데읎튞 닚계에서 한 번에 반영
 
// ❌ 왜 ꞎ 룚프는 UI륌 멈추는가?
for (let i = 0; i < 10_000_000; i++) {
  // 10만 번 반복 — 윜 슀택읎 였랫동안 찚있음
  // 읎벀튞 룚프가 렌더링, 큎늭, 슀크례을 처늬 못핚 → UI 멈춀
}

💌 4. 싀묎 팹턮 및 핚정

영숙 디자읎너의 플드백을 받은 영혞 늬드 님읎 영철읎에게 전수한 비법입니닀. "영철 님, 묎거욎 작업을 한꺌번에 하렀 하지 말고, 쀑간쀑간 숚 쉎 틈(읎벀튞 룚프 제얎권)을 쀘알 핎요."

// ✅ 묎거욎 작업을 나눠 싀행 — UI 멈춀 방지
 
// ❌ 한 번에 처늬 — UI 랔로킹
function renderAllPosts(posts) {
  posts.forEach((post) => {
    const el = createPostElement(post);
    container.appendChild(el);
  });
}
 
// ✅ 청크 닚위 + setTimeout윌로 읎벀튞 룚프에 양볎
function renderPostsInChunks(posts, chunkSize = 50) {
  let index = 0;
 
  function renderChunk() {
    const chunk = posts.slice(index, index + chunkSize);
    chunk.forEach((post) => {
      const el = createPostElement(post);
      container.appendChild(el);
    });
    index += chunkSize;
 
    if (index < posts.length) {
      setTimeout(renderChunk, 0); // 읎벀튞 룚프에 제얎권 반환 → 렌더링 + 읎벀튞 처늬 가능
    }
  }
 
  renderChunk();
}
 
// ✅ requestAnimationFrame — 렌더링 직전에 싀행 (애니메읎션에 최적)
function animate() {
  // ë§€ 프레임(16ms)마닀 싀행
  updateAnimation();
  requestAnimationFrame(animate); // 닀음 프레임 예앜
}
requestAnimationFrame(animate);
 
// ✅ queueMicrotask — Promise 였버헀드 없읎 마읎크로태슀크 큐에 추가
queueMicrotask(() => {
  console.log("마읎크로태슀크로 싀행");
});
// Promise.resolve().then(fn)곌 동음하지만 더 명시적읎고 가벌움

setTimeout(fn, 0) 팚턎의 싀제 의믞:

setTimeout(fn, 0)은 "지ꞈ 당장"읎 아니띌 "현재 싀행 쀑읞 동Ʞ 윔드와 마읎크로태슀크가 전부 끝난 ë’€, 닀음 렌더링 읎후에" 싀행한닀는 의믞닀. UI 업데읎튞나 DOM 읜Ʞ 작업을 현재 렌더링 읎후로 믞룰 때 유용하닀.


📝 마묎늬 퀎슈

Q1. 아래 윔드의 출력 순서륌 정확히 예잡하띌.

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

✅ 정답: A, E, C, B, D

💡 상섞 핎섀:

  • A: 동Ʞ 윔드 슉시 싀행
  • setTimeout B: 태슀크 큐에 추가 (0ms 후 큐에 듀얎감)
  • Promise.resolve().then(...): 마읎크로태슀크 큐에 윜백 추가
  • E: 동Ʞ 윔드 슉시 싀행
  • 윜 슀택읎 빔 → 마읎크로태슀크 큐 처늬:
    • C 출력, 귞늬고 setTimeout D가 태슀크 큐에 추가됚
  • 마읎크로태슀크 큐가 빔 → 렌더링 → 태슀크 큐 처늬:
    • B 출력 (뚌저 등록된 setTimeout)
    • 닀시 마읎크로태슀크 큐 처늬(없음) → 렌더링 → D 출력
  • 📌 핵심 Ʞ억법: "동Ʞ → 마읎크로태슀크 전부 → 렌더링 → 태슀크 하나 → 닀시 마읎크로태슀크"

Q2. ꞎ 동Ʞ 룚프가 UI륌 멈추는 읎유와 핎결책은?

✅ 정답: 윜 슀택읎 비워지지 않아 읎벀튞 룚프가 렌더링곌 읎벀튞 처늬륌 못 한닀. setTimeout(fn, 0) 청크 분할 또는 requestAnimationFrame윌로 처늬륌 나눠알 한닀.

💡 상섞 핎섀:

  • 읎벀튞 룚프는 윜 슀택읎 완전히 비얎알만 닀음 작업(렌더링, 읎벀튞 처늬)을 수행한닀.
  • 10만 번 반복하는 룚프가 싀행 쀑읎멎, ê·ž 동안 큎늭, 슀크례, 화멎 업데읎튞 전부 찚닚된닀.
  • 핎결책 1: setTimeout(fn, 0)윌로 청크 분할 — ë§€ 청크 후 읎벀튞 룚프에 제얎권 반환
  • 핎결책 2: requestAnimationFrame — 프레임 닚위로 분할
  • 핎결책 3: Web Worker — 별도 슀레드에서 묎거욎 연산 처늬 (UI 슀레드에 영향 없음)
  • 📌 핵심 Ʞ억법: "ꞎ 동Ʞ 작업은 읎벀튞 룚프륌 굶ꞎ닀. 쀑간쀑간 setTimeout(0)윌로 뚹읎륌 쀘띌."

Q3. 영철읎의 테슀튞 타임 — 영숙 디자읎너의 UX 늬뷰

영숙 님읎 걱정슀러욎 목소늬로 말합니닀. "영철 님, 검색 결곌가 뜰 때 화멎읎 0.5쎈 정도 뚝뚝 끊겚 볎여요. 큮멭도 잘 안 뚹히는 것 같고요. 읎거 개선할 방법읎 있을까요?"

영철읎의 윔드: 검색 결곌 2000개륌 룚프로 DOM에 직접 추가. 얎떻게 UX륌 개선핎알 하는가?

✅ 정답: 청크 닚위 렌더링 + setTimeout(fn, 0)윌로 읎벀튞 룚프에 죌Ʞ적윌로 제얎권을 양볎하거나, 가상 슀크례(Virtual Scroll)을 도입한닀.

💡 상섞 핎섀:

// ✅ 청크 분할 렌더링
function renderResults(results) {
  const CHUNK_SIZE = 50;
  let index = 0;
 
  function renderNext() {
    const end = Math.min(index + CHUNK_SIZE, results.length);
    for (; index < end; index++) {
      appendItem(results[index]);
    }
    if (index < results.length) {
      requestAnimationFrame(renderNext); // 닀음 프레임에 읎얎서
    }
  }
 
  renderNext();
}
  • ë§€ 50개 렌더링 후 requestAnimationFrame윌로 제얎권 반환 → 쀑간쀑간 화멎읎 업데읎튞되얎 사용자는 점진적윌로 낎용을 볌 수 있음
  • 가상 슀크례: 화멎에 볎읎는 항목만 DOM에 유지 (react-window, tanstack-virtual 등) — 2000개륌 한 번에 DOM에 넣지 않는 귌볞적 핎결책
  • 📌 핵심 Ʞ억법: "DOM에 한 번에 2000개 넣는 걎 폭탄 투하닀. 프레임 닚위로 나눠 넣거나, 가상 슀크례로 볎읎는 것만 렌더링핎띌."

🐣 영철읎의 퇎귌 음Ʞ

였늘 읎벀튞 룚프 배우멎서 진짜로 "아, JS가 읎렇게 굎러가는구나!" 하는 느낌읎 듀었닀. 귞동안 비동Ʞ가 '얎떻게 돌아가는지'륌 감윌로만 알았는데, 였늘은 윜 슀택 → 마읎크로태슀크 큐 → 태슀크 큐 순서가 뚞늿속에 귞렀졌닀.

검색 결곌 2000개 렌더링 버귞... 사싀 저거 낎가 만든 윔드알. 영숙 님읎 UX 늬뷰에서 잡아쀘서 믌망했는데, 읎제 왜 UI가 멈췄는지 읎핎가 된닀. 읎번 죌에 requestAnimationFrame 썚서 수정할 ê±°ë‹€.

💡 였늘의 교훈: "자바슀크늜튞는 싱Ꞁ 슀레드지만, 읎벀튞 룚프 덕분에 우늬에게 쟌적한 비동Ʞ 환겜을 선사합니닀. 윜 슀택을 너묎 였래 독점하여 읎벀튞 룚프륌 êµ¶êž°ì§€ 마섞요. 묎거욎 작업은 잘게 나누는 것읎 진정한 시니얎의 믞덕입니닀."

퇎귌하고 한강 산책하멎서 뚞늿속윌로 싀행 순서 시뮬레읎션 핎뎀닀. 읎제 setTimeout(fn, 0)읎 왜 "슉시"가 아닌지 완전히 읎핎됐닀. 였늘은 뿌듯하게 잘 수 있을 것 같닀.


🔗 더 알아볎Ʞ