Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions content-ru/.translation-manifest.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"browser/Event Loop/Event Loop.md": "ef7e18bdfa11924dff151a6a5557be2893e4004c9c10068ffb553f0008c36225",
"browser/Event Loop/Rendering Pipeline.md": "b698d9525fdb3aea1de4b3243b52c8bc411ad011ace105e9388a18ea704192d6",
"browser/Event Loop/index.md": "00cc102dabdebcb267cbfa6c0f10ca45571ecb015de4a2ac221857421f2e9db4",
"browser/Event Loop/Event Loop, Microtasks, Macrotasks.md": "ef7e18bdfa11924dff151a6a5557be2893e4004c9c10068ffb553f0008c36225",
"browser/Event Loop/Rendering Pipeline.md": "272e73f40aadfd7fbe43603dc75f044beee1672196e992f76f240937a0cb8bd6",
"browser/Event Loop/index.md": "f9f7836aceeba96e0518a91f66bb916e1d194bc7e52f58ba2e8684a643866840",
"browser/Network/CORS.md": "90a34e5d48b1faad3fdb74ecb7b41fa0b78bb87af7e157c439f2c537d60632a8",
"browser/Network/Cookies & Storage.md": "9a49a4e936342471acae3b88e5f5f81f9c2c7c83e14be4a19dbe3acb88330d87",
"browser/Network/HTTP Headers & Caching.md": "84d40df2788021f2466cb63770346463df8cab99283ec7f78ed57026ee035c09",
"browser/Network/TCP & TLS Handshake.md": "40b29f2a89a82ff105ec0b9e581a3b0bf7b6d0b27937deebb636225c2fd8db16",
"browser/Network/index.md": "8562b0aabb62de7630dcf0b354e7c903792255721a1c57f519b4f1245651e483",
"browser/index.md": "36cb2ffc1ebf165c0ceabc5aab8c514d292f06d148fdb359ac03da0a14964313",
"browser/index.md": "2eb4623fd4287396fe34aa7ce75076ae5e30f88af39c8a4e3eab03b3530ebab9",
"headhunter/JavaScript-easy-level.md": "07d772fee0195708ac9b6bdf41e1f0fcd2efcf15861cc34947dbce0975d26277",
"headhunter/JavaScript-middle-level.md": "46ae57febadc70bebdc4f8116a7cf2db901c525e2b573bcfa34377be7af45ac0",
"headhunter/index.md": "d9888f62e5b1a3e71f67b05616d51e15e1eccb67be651c33da850f48f438afd2",
Expand Down Expand Up @@ -99,6 +99,6 @@
"leetcode/index.md": "88c151c7c5564227dea91e47d96076632d854dd7fc1eddfa45440093be401dc3",
"vue/Internals/Compiler.md": "c46b3dd12912b11bc4f0a34b2abba574baff9626f42df410eab279ae77b99e90",
"vue/Internals/Reactivity.md": "9b502f513195b4512f4d8188f1ac30746030e1c5a52a6a626d51c80c15752d24",
"vue/index.md": "aa6244e7ef392b574ddd86f1cafeeac49e03dda7df6eb83b1767e493d1059669",
"vue/Internals/index.md": "0637645f58c346554c1687702236afa74ed3b25e795a4c0ad5038369ed0479fb"
"vue/Internals/index.md": "0637645f58c346554c1687702236afa74ed3b25e795a4c0ad5038369ed0479fb",
"vue/index.md": "aa6244e7ef392b574ddd86f1cafeeac49e03dda7df6eb83b1767e493d1059669"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# ⏱ Event Loop, микротаски, макротаски, requestAnimationFrame

## 💡 Основная идея

> Event Loop не выполняет код. Это диспетчер: он проверяет, пуст ли стек вызовов, и затем перемещает следующую задачу из очереди в него.

Два вопроса, на которые отвечает эта заметка: почему `setTimeout(fn, 0)` всегда выполняется после всего синхронного кода _и_ всех промисов? И почему бесконечная цепочка `Promise.then()` намертво замораживает вкладку, а бесконечный цикл `requestAnimationFrame` — нет?

---

# 🧭 Участники

1. **Call Stack (стек вызовов)** — выполняет синхронный JS построчно. Пока он не пуст, ничего другого не выполняется.
2. **JS-движок (V8 и т.д.)** — выполняет только стек вызовов. У него нет встроенного понятия `setTimeout`, DOM или `fetch` — ничего из этого не входит в спецификацию ECMAScript.
3. **Web API** (браузер) / **libuv** (Node.js) — отдельная часть рантайма, которая фактически отсчитывает таймеры, слушает сетевые/DOM-события и подготавливает кадры.
4. **Очередь макротасков** — `setTimeout`, `setInterval`, DOM-события, I/O. FIFO; Event Loop извлекает из неё **по одной задаче за раз**.
5. **Очередь микротасков** — `Promise.then`, `queueMicrotask`, `MutationObserver`. FIFO, но Event Loop **полностью опустошает её**, прежде чем делать что-либо ещё.
6. **Event Loop** — сам диспетчер: в цикле проверяет «пуст ли стек вызовов?», извлекает следующую задачу, помещает её в стек.

---

# 🔑 Золотое правило

> После каждой синхронной задачи Event Loop **полностью опустошает** очередь микротасков — включая микротаски, добавленные во время самого опустошения. Только когда очередь пуста, он переходит дальше (к рендерингу или к одному макротаску).

Разница не в «приоритете» — а в разной стратегии опустошения: микротаски означают _«заверши всё, что накопилось, сколько бы там ни оказалось»_; макротаск означает _«возьми ровно одну вещь и верни управление диспетчеру»_.

**Одна полная итерация цикла:**

```
Call Stack (весь синхронный код)
→ полностью опустошить очередь микротасков (даже если она пополняется во время опустошения)
→ колбэки requestAnimationFrame (снимок ровно этого кадра)
→ Render: Layout → Paint → Composite
→ взять ОДИН макротаск → выполнить его
→ снова опустошить очередь микротасков
→ ... повторить
```

---

# 🎞 requestAnimationFrame

`requestAnimationFrame(cb)` просит браузер _«выполнить это прямо перед отрисовкой следующего кадра»_. Это не таймер, и он не даёт гарантий по точным миллисекундам — он привязан к реальному циклу обновления экрана (~60/сек).

Порядок выполнения с учётом rAF:

```js
Promise.resolve().then(() => console.log("promise"))
requestAnimationFrame(() => console.log("raf"))
setTimeout(() => console.log("timeout"), 0)

// promise → raf → timeout
```

---

# ❄️ Почему бесконечный Promise.then() замораживает вкладку, а бесконечный rAF — нет

Это не совпадение — обе очереди работают по принципиально разной механике.

**Очередь микротасков проверяется динамически.** Перед каждым шагом Event Loop спрашивает «очередь пуста?». Если выполняющийся сейчас микротаск добавляет в очередь новый, тот присоединяется к очереди и выполняется **в этом же проходе**. Для бесконечной цепочки условие выхода («пусто») никогда не наступает:

```js
function loop() {
Promise.resolve().then(loop) // каждый .then() порождает следующий
}
loop()
// Event Loop никогда не доходит до рендеринга, макротасков или кликов — вкладка мертва
```

**Очередь rAF для кадра — это снимок (батч), зафиксированный в начале обработки этого кадра.** Всё, что регистрируется через `requestAnimationFrame(...)` _во время_ выполнения текущего батча, физически не может попасть в этот же батч — только в следующий кадр:

```js
function animate() {
x++
requestAnimationFrame(animate) // попадёт в СЛЕДУЮЩИЙ кадр, не в этот
}
requestAnimationFrame(animate)
// Render (Layout → Paint → Composite) гарантированно происходит между кадрами
```

Поэтому бесконечная рекурсия через rAF не блокирует браузер: рендер всегда происходит между итерациями. Ключевое различие не в том, насколько быстро выполняется каждый колбэк — а в том, что у очереди микротасков нет границы-снимка, а у батча rAF она есть.

---

# 🧠 Одним предложением

> Условие выхода Event Loop для очереди микротасков — «пусто», а самопополняющаяся очередь никогда его не достигает. Очередь rAF же выходит через _снимок_: всё, что добавляется во время батча, откладывается на следующий, независимо от скорости выполнения.

[[browser/Event Loop/index|Event Loop & Rendering]]
[[browser/Event Loop/Rendering Pipeline|Rendering Pipeline — что происходит после опустошения очереди микротасков]]
#browser
93 changes: 0 additions & 93 deletions content-ru/browser/Event Loop/Event Loop.md

This file was deleted.

Loading