Event loop best explanation ever - https://youtu.be/eiC58R16hb8?si=uv2bOdlrUJJEgP1J&t=652 JS execution flow is based on an endless event loop JS executes tasks one by one starting from the oldest When there are no tasks anymore JS waits for new ones Task may come while the engine is busy, then it's queued Queue of tasks is called macrotask queue Rendering happens only after the task is completed, before another macrotask If a task takes long, the browser is blocked & raises the "page unresponsiveā alert Macrotasks Scripts we call Event handlers Scripts are added to the end of the macrotask queue by setTimeout(func) with no delay Microtasks After every macrotask, tasks from microtask queue are executed It's done before running other macrotasks or rendering or event handling It guarantees that the environment is the same between microtasks (no mouse coordinate changes, no new network data, etc) Microtask is a script called by promise handlers .then/catch/finally() or queueMicrotask(func) or observers Microtasks are used behind of await as well queueMicrotask() So if we'd like to execute a function asynchronously (after the current code), but before changes are rendered or new events handled, we can schedule it with queueMicrotask(() => { func() }) Event loop sequence M a crotask (script, event handler) M i crotask (promise handlers & queueMicrotask(func) ) Render M a crotask set by setTimeout(func) ... again & again Web workers For calculations that shouldn't block the event loop, we can use Web Workers. That's a way to run code in another parallel thread Web Workers can exchange messages with the main process They have their own variables, and their own event loop. Web Workers do not have access to DOM They are useful, mainly, for calculations They can use multiple CPU cores simultaneously in NodeJS process.nextTick(func) executes function on the current iteration of the event loop, after the current operation ends, before setTimeout() and setImmediate() setImmediate(func) is the same as setTimeout(func, 0) and executes in the next iteration of the event loop, as soon as possible Examples Sequence
function Cmpt0() {
function func() {
alert(1) // synchronous call
setTimeout(() => alert(2)) // macrotask sent to the end of the queue
Promise.resolve().then(res => alert(3)) // microtask
alert(4) // regular synchronous call
// 1 --> 4 --> 3 --> 2
}
return <button onClick={func}>Click</button>
}
const toRender0 = <Cmpt0 />
Count to billion without setTimeout() Run whole code at one time Changes to DOM are painted after running task is completed We'll see only the last value instead of progress Code freezes the browser
function Cmpt2() {
const ref = React.useRef()
function countToBln() {
let count = 0, start = Date.now()
for (let j = 0; j < 1e9; j++) count++
ref.current.innerHTML = count
alert(`Done in ${Date.now() - start} ms`)
}
return (
<div>
<div ref={ref}>0</div>
<button onClick={countToBln}>Click</button>
</div>
)
}
const toRender2 = <Cmpt2 />
Count to billion with setTimeout() Split code into parts and queue them: 1 mln + 1 mln + ... up to 1 bln Splitting with setTimeout() we make multiple macrotasks and changes are painted in-between If an onclick event appears while the engine is busy it is queued mixed together with main counting tasks Page is responsive There's in-browser minimal delay of 4ms for many nested setTimeout calls and the earlier we schedule task via setTimeout, the faster it runs
function Cmpt3() {
const ref = React.useRef()
function func() {
let i = 0, j = 0, start = Date.now()
function countToMln() {
for (let k = 0; k < 1e6; k++) i++
ref.current.innerHTML = i
}
function countToBln() {
if (i < 1e9) {
setTimeout(countToBln) // schedule the new call // 1000 calls
j++
}
if (i === 1e9) {
alert(`Done in ${Date.now() - start} ms with ${j} timeout() calls`)
return
}
countToMln()
}
countToBln()
}
return (
<div>
<div ref={ref}>0</div>
<button onClick={func}>Click</button>
</div>
)
}
const toRender3 = <Cmpt3 />
All microtasks runs before render This code acts as a synchronous, window is frozen
function Cmpt1() {
const ref = React.useRef()
let i = 0
function count() {
do {
i++
ref.current.innerHTML = i
} while (i % 1e3 !== 0)
if (i < 1e6) queueMicrotask(count)
}
return (
<div>
<div ref={ref}>0</div>
<button onClick={count}>Click</button>
</div>
)
}
const toRender1 = <Cmpt1 />
Let event bubble Schedule an action until the event bubbled up and was handled on all levels.
menu.onclick = function() {
let customEvent = new CustomEvent("menu-open", { bubbles: true }) // create a custom event with the clicked menu item data
setTimeout(() => menu.dispatchEvent(customEvent)) // dispatch the custom event asynchronously
}