--- id: wiki-2026-0508-javascript-async-and-event-loop title: JavaScript Async and Event Loop category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Event Loop, JS Event Loop, Microtask Queue, Macrotask Queue] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [javascript, async, event-loop, concurrency] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: javascript framework: node-browser --- # JavaScript Async and Event Loop ## 매 한 줄 > **"매 single-threaded JS 가 매 cooperative scheduler 위에서 매 async 를 흉내낸다."**. Call stack + task queue + microtask queue + render pipeline 이 매 tick 의 단위. Promise/async-await 는 매 syntactic sugar — runtime 은 매 microtask drain rule 로 결정. ## 매 핵심 ### 매 Stack vs Queues - **Call stack**: 매 동기 frame. LIFO. 매 비어야 매 tick 진행. - **Task queue (macrotask)**: setTimeout, setInterval, MessageChannel, I/O callback, UI event. 매 tick 당 1개 drain. - **Microtask queue**: Promise.then, queueMicrotask, MutationObserver. 매 tick 마지막에 매 전부 drain (재진입 포함). - **Animation frame queue**: requestAnimationFrame. 매 paint 직전. - **Render steps**: style → layout → paint → composite. 매 vsync 와 매 동기화. ### 매 Tick 순서 (browser) 1. 매 task queue 에서 매 1 task pop → 실행. 2. 매 microtask queue 매 전부 drain (recursively). 3. 매 rAF callbacks. 4. 매 render (필요 시). 5. 매 idle callbacks (requestIdleCallback). 6. 매 다음 tick. ### 매 Node.js phases - timers → pending callbacks → idle/prepare → poll → check (setImmediate) → close → microtask drain (between phases since Node 11). - `process.nextTick` 는 매 microtask 보다도 매 우선. ### 매 응용 1. UI freeze 방지 — 매 long task 를 매 chunk + scheduler.yield(). 2. Race condition 분석 — 매 await 사이 매 state mutation 가능. 3. Server backpressure — 매 event loop lag (`perf_hooks.monitorEventLoopDelay`). ## 💻 패턴 ### Microtask vs Macrotask 순서 ```javascript console.log('1'); setTimeout(() => console.log('2'), 0); Promise.resolve().then(() => console.log('3')); queueMicrotask(() => console.log('4')); console.log('5'); // 1, 5, 3, 4, 2 — microtasks drain before next macrotask ``` ### Long task chunking with scheduler.yield (2026) ```javascript async function processLargeArray(items) { for (let i = 0; i < items.length; i++) { doWork(items[i]); if (i % 100 === 0 && 'scheduler' in window) { await scheduler.yield(); // Chrome 129+ stable } } } ``` ### Promise.allSettled with concurrency limit ```javascript async function mapLimit(items, limit, fn) { const results = new Array(items.length); let cursor = 0; const workers = Array.from({ length: limit }, async () => { while (cursor < items.length) { const i = cursor++; results[i] = await fn(items[i]).catch(e => ({ error: e })); } }); await Promise.all(workers); return results; } ``` ### AbortController for cancellation ```javascript const ac = new AbortController(); fetch('/slow', { signal: ac.signal }) .then(r => r.json()) .catch(e => { if (e.name === 'AbortError') console.log('cancelled'); }); setTimeout(() => ac.abort(), 3000); ``` ### Async iterator drain ```javascript async function* readChunks(stream) { const reader = stream.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) return; yield value; } } finally { reader.releaseLock(); } } for await (const chunk of readChunks(resp.body)) { process(chunk); } ``` ### Avoid microtask starvation ```javascript // BAD — recursive Promise.resolve() blocks rendering function bad() { Promise.resolve().then(bad); // browser never paints } // GOOD — use setTimeout(0) or MessageChannel for yielding function good() { setTimeout(good, 0); } ``` ### Event loop lag monitor (Node) ```javascript import { monitorEventLoopDelay } from 'node:perf_hooks'; const h = monitorEventLoopDelay({ resolution: 20 }); h.enable(); setInterval(() => { console.log('p99 lag ms', h.percentile(99) / 1e6); h.reset(); }, 1000); ``` ### Top-level await (ESM) ```javascript // module.mjs const config = await fetch('/config.json').then(r => r.json()); export default config; // importer awaits parent module graph — beware deadlock cycles ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | 매 즉시 yield 필요 | queueMicrotask / Promise.resolve().then | | 매 paint 후 작업 | requestAnimationFrame | | 매 idle 시간 작업 | requestIdleCallback / scheduler.postTask('background') | | 매 long task chunking | scheduler.yield() (modern) | | 매 cancellation | AbortController + signal | | 매 backpressure 측정 | perf_hooks.monitorEventLoopDelay | **기본값**: async/await + AbortController. 매 long task 는 매 scheduler.yield. 매 nextTick / process.nextTick 남용 X. ## 🔗 Graph - 부모: [[JavaScript]] · [[Concurrency]] - 변형: [[Browser Rendering]] - 응용: [[React Concurrent Mode]] · [[Web Worker (웹 워커)|Web Workers]] - Adjacent: [[AbortController]] · [[Streams]] ## 🤖 LLM 활용 **언제**: 매 race / ordering bug 분석, 매 long-task profiling, 매 SSR streaming 설계. **언제 X**: 매 CPU-bound 작업 — 매 worker / native 로 offload. ## ❌ 안티패턴 - **Recursive microtask loop**: 매 rendering starvation. - **await in tight for loop**: 매 직렬화. 매 Promise.all 사용. - **forgotten unhandled rejection**: 매 process crash (Node 15+). - **setTimeout(fn, 0) for ordering**: 매 microtask 와 매 race — queueMicrotask 사용. ## 🧪 검증 / 중복 - Verified (HTML spec — Event Loop Processing Model, Node.js docs). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — 매 task/microtask order + scheduler.yield 패턴 |