πŸ₯‹ Code Dojo

μ‹€μ œ ν”„λ‘œλ•μ…˜κΈ‰ μ½”λ“œλ₯Ό 읽고, κ·Έ λ°‘μ˜ 사고λͺ¨λΈ(사상)을 보고, νƒœλΈ”λ¦Ώμ—μ„œ β–Ά 눌러 직접 λŒλ €λ³Έλ‹€. νŠΈλž™ β†’ 레벨(κΈ°λ³Έ λ¬Έλ²•β†’ν”„λ‘œλ•μ…˜) β†’ 레슨. μ „λΆ€ μ˜μ–΄.

β‘  Basics β€” κΈ°λ³Έ 문법·idiom
β‘‘ Core β€” 핡심 κ°œλ…Β·μ‚¬μƒ
JavaScript Β· TypeScript

The event loop β€” why a promise beats setTimeout(0)

JavaScript is single-threaded: async callbacks wait in queues. Microtasks (promises) are fully drained before any macrotask (setTimeout). So a promise scheduled LATER still runs before a 0ms timer scheduled earlier. Run it and read the order.

사상 (principles)
Explicit over implicit β€” Obvious beats clever; no hidden side effects or magic.

There are two queues with different priority. After the synchronous code finishes, the engine drains ALL microtasks (promise callbacks), then takes ONE macrotask (a timer/event). Output order is 1, 2, 3, 4 β€” the promise (3) jumps ahead of the timer (4).

πŸ”‘ 싀무 μš©μ–΄ (say these in English)
event loopβ€” one thread runs code, then drains queued callbacks
πŸ—£β€œJS is single-threaded β€” the event loop runs my code, then drains queued callbacks.”
microtask vs macrotaskβ€” promises run before timers
πŸ—£β€œPromise callbacks are microtasks and drain before any setTimeout macrotask.”
call stackβ€” sync frames run to completion first
πŸ—£β€œSynchronous frames run to completion on the call stack before the loop continues.”
non-blocking I/Oβ€” async work yields so the thread stays free
πŸ—£β€œAsync I/O yields the thread so the UI stays responsive.”
βš–οΈ νŠΈλ ˆμ΄λ“œμ˜€ν”„ Β· 원리
  • A resolved promise's callback runs on the microtask queue, which the event loop fully drains after the current synchronous frame but BEFORE rendering, I/O, or any macrotask, whereas setTimeout(0) is a macrotask clamped to a ~4ms minimum and queued behind already-pending timers and rendering β€” so a promise wins precisely because microtasks have strictly higher priority than the timer phase.
  • The cost of that priority is starvation: because the loop empties the entire microtask queue before yielding, a chain of promises that keep enqueuing more microtasks can block rendering and starve I/O indefinitely, which is exactly the failure mode setTimeout(0) avoids by surrendering control back to the loop after one task.
  • Reach for queueMicrotask/Promise.resolve().then() when you need to defer work but still land before the next paint β€” e.g. batching state mutations or guaranteeing a callback runs after the current call stack unwinds without ever yielding to the browser.
  • Use setTimeout(0) (or better, requestAnimationFrame for visual work, or MessageChannel to dodge the 4ms clamp) precisely when you WANT to yield β€” to let the browser paint, process input, or break up a long task so the main thread stays responsive; choosing a microtask there is the bug, not the optimization.

μ€„λ³„λ‘œ μ˜μ–΄λ‘œ λ§ν•˜κΈ° πŸ—£

Promise.resolve().then(() => out.push("3 microtask"))
πŸ—£A resolved promise's callback is a microtask β€” it runs after the synchronous code but before any timer.
setTimeout(() => out.push("4 macrotask"), 0)
πŸ—£Even a zero-millisecond timeout is a macrotask, so it waits until every microtask is drained β€” that's why three prints before four.

더 λ§Žμ€ μŠ€νƒ(Next.jsΒ·JavaΒ·SQLΒ·GoΒ·Python)κ³Ό λ°±μ—”λ“œ νλ¦„λ„λŠ” 계속 μΆ”κ°€λ©λ‹ˆλ‹€.