Series: JavaScript Basics Lesson 46

Event Loop

Event Loop'ning oddiy qoidasi (Stack bo'sh bo'lsa, Queue'dan ol), Microtask va Macrotask navbatlari o'rtasidagi prioritet, har bir Macrotask'dan keyin Microtask Queue to'liq bo'shatilishi, brauzerdagi rendering bosqichi, va Microtask Queue V8'da, Macrotask esa runtime'da joylashgani.

46-dars

Event Loop

Event Loop — bu alohida "murakkab mexanizm" emas. Aslida u juda oddiy, cheksiz tsikl (infinite loop), faqat bitta ishni takrorlaydi:

"Call Stack bo'sh-mi? Bo'sh bo'lsa — Queue'da kutayotgan ish bor-mi? Bor bo'lsa — uni Call Stack'ga qo'y."

Shu, bor-yo'g'i shu. Lekin bu oddiy qoida ortida — aniq tartib (priority) va bosqichlar yotadi, va aynan shular chalkashtiradi. Keling, tartib bilan ochamiz.


1. Uchta asosiy "joy" — takrorlaymiz

Oldingi mavzuda ko'rgan uchta narsani eslaylik:

  1. Call Stack — hozir bajarilayotgan kod shu yerda
  2. Web APIs — og'ir/kutish ishlari shu yerda (fon)
  3. Queue(lar) — tayyor bo'lgan callback'lar shu yerda kutadi

Event Loop — bu 3-Queue bilan 1-Call Stack o'rtasidagi "nazoratchi".


2. Muhim: Queue bitta emas, ikkita (aslida undan ko'p)

Bu joy — Event Loop'ni tushunishning kaliti. Ko'p odam "bitta Callback Queue bor" deb o'ylaydi — bu noto'g'ri. Amalda kamida ikkita navbat bor, va ular teng huquqli emas:

┌─────────────────────────┐
│   Microtask Queue        │  ← YUQORI PRIORITET
│   (Promise.then, catch,  │
│    finally, queueMicro-  │
│    task, async/await)    │
└─────────────────────────┘

┌─────────────────────────┐
│   Macrotask Queue        │  ← PASTROQ PRIORITET
│   (setTimeout, setInter- │
│    val, DOM eventlar,    │
│    I/O)                  │
└─────────────────────────┘

Bu ikkisining farqini keyingi mavzuda (Microtask vs Macrotask) alohida chuqur ochamiz — hozir esa Event Loop ularni qanday tartibda tekshirishini ko'ramiz.


3. Event Loop algoritmi — aniq qadamlar

1. Call Stack'da bajarilayotgan kod bo'lsa — uni oxirigacha bajar (hech narsaga aralashma)
2. Call Stack bo'shagach:
   a. Microtask Queue'ni TO'LIQ bo'shat (har bir microtask ichida yangi
      microtask qo'shilsa, ularni HAM bajar — Queue butunlay tugagunicha)
   b. Endi bitta Macrotask'ni Queue'dan ol, Call Stack'ga qo'y, bajar
   c. Macrotask tugagach — yana (a) ga qaytib, Microtask Queue'ni tekshir
3. Shu tsiklni cheksiz takrorla

Eng muhim qoida: har bir Macrotask'dan keyin, keyingi Macrotask'ga o'tishdan oldin, Microtask Queue albatta TO'LIQ bo'shatiladi.


4. Misolda ko'ramiz

console.log('1 - sync');

setTimeout(() => {
  console.log('2 - macrotask');
}, 0);

Promise.resolve().then(() => {
  console.log('3 - microtask');
});

console.log('4 - sync');

Qadamlar:

Qadam 1 — console.log('1') — Call Stack'da, darhol bajariladi → 1 - sync

Qadam 2 — setTimeout(fn, 0) chaqiriladi. 0ms bo'lsa ham, fn darhol Call Stack'ga tushmaydi — u Web API'ga topshiriladi, u yerdan (0ms o'tgach, deyarli darhol) Macrotask Queue'ga tushadi.

Qadam 3 — Promise.resolve().then(fn) — fn Microtask Queue'ga tushadi.

Qadam 4 — console.log('4') — darhol bajariladi → 4 - sync

Qadam 5 — Call Stack bo'shaydi. Endi Event Loop ishga tushadi:

  • Avval Microtask Queue'ni tekshiradi → u yerda 3 - microtask bor → bajaradi → 3 - microtask
  • Microtask Queue bo'shaydi (boshqa microtask yo'q)
  • Endi Macrotask Queue'dan bittasini oladi → 2 - macrotask → bajaradi → 2 - macrotask

Yakuniy natija:

1 - sync
4 - sync
3 - microtask
2 - macrotask

Diqqat qiling: setTimeout(fn, 0) yozilgan bo'lsa ham, u Promise.then'dan keyin chiqdi — chunki Microtask har doim Macrotask'dan oldin ishlaydi, delay qanchalik kichik bo'lishidan qat'i nazar.


5. "Microtask Queue TO'LIQ bo'shatiladi" — nega bu muhim?

Bu qoidani chuqurroq ko'ramiz, chunki bu yerda ko'p dasturchi adashadi:

console.log('boshlandi');

Promise.resolve().then(() => {
  console.log('microtask 1');
  Promise.resolve().then(() => {
    console.log('microtask 2 (ichkarida yaratilgan)');
  });
});

setTimeout(() => {
  console.log('macrotask');
}, 0);

console.log('tugadi');

Natija:

boshlandi
tugadi
microtask 1
microtask 2 (ichkarida yaratilgan)
macrotask

microtask 2 — microtask 1 bajarilayotganda, ya'ni Event Loop Microtask Queue'ni "tozalash" jarayonining o'rtasida qo'shildi. Lekin Event Loop qoidasi qattiq: Macrotask'ga o'tishdan oldin, Queue butunlay bo'sh bo'lishi kerak — hatto agar bajarish jarayonida yangi microtask qo'shilsa ham, ular ham kutib olinadi. Shuning uchun macrotask — eng oxirida chiqadi.

Amaliy xavf: agar .then() ichida doimo yangi Promise yaratib, o'ziga-o'zi microtask qo'shaversangiz — bu "microtask starvation" deb ataladi: Macrotask Queue (masalan, setTimeout, hatto rendering ham!) hech qachon navbatga yeta olmaydi, chunki Microtask Queue hech tugamaydi.


6. Brauzerda qo'shimcha qatlam — Rendering

Brauzerda Event Loop'ning har bir Macrotask aylanishidan keyin, yana bir imkoniyat bor: rendering (qayta chizish). To'liq tartib quyidagicha:

1. Bitta Macrotask bajariladi
2. Microtask Queue to'liq bo'shatiladi
3. Agar kerak bo'lsa — brauzer sahifani qayta render qiladi
   (requestAnimationFrame shu yerda ishga tushadi)
4. Keyingi Macrotask'ga o'tiladi

Aynan shuning uchun setTimeout(fn, 0) "darhol" ishlamaydi — undan oldin microtasklar, undan oldin/keyin esa render jarayoni bo'lishi mumkin. Va aynan shuning uchun Microtask Queue'ni cheksiz to'ldirish — sahifani "muzlatib qo'yadi": render navbatga hech qachon yetib bormaydi.


7. V8 darajasida — bu qayerda "jismonan" joylashgan?

Aniqlik uchun: Event Loop'ning o'zi V8'ning bir qismi emas! V8 — faqat Call Stack va Heap'ni boshqaradi, kodni bajaradi. Event Loop — bu runtimening (brauzer yoki Node.js) vazifasi:

  • Brauzerda — Event Loop'ni brauzer o'zi boshqaradi (har bir tab — o'z Event Loop'iga ega)
  • Node.js'da — Event Loop'ni libuv kutubxonasi boshqaradi

Lekin Microtask Queue bundan mustasno — u V8 Engine'ning o'zi ichida, ECMAScript spec darajasida belgilangan (chunki Promise — til standarti). Shuning uchun V8 har safar Call Stack bo'shaganda, avval o'zining ichki Microtask Queue'sini tekshiradi — bu runtime (brauzer/Node)dan mustaqil, til darajasidagi majburiyat. Macrotask Queue esa — to'liq runtimening mas'uliyati.

┌──────────────────────────────────────────┐
│               Runtime (brauzer/Node)       │
│                                            │
│  ┌──────────────────────────────────┐    │
│  │            V8 Engine              │    │
│  │  Call Stack                       │    │
│  │  Heap                             │    │
│  │  Microtask Queue (spec darajasida)│    │
│  └──────────────────────────────────┘    │
│                                            │
│  Event Loop (runtime boshqaradi):         │
│  Macrotask Queue                          │
│  Rendering pipeline                       │
│  Web APIs / libuv                         │
└──────────────────────────────────────────┘

Xulosa

Event Loop — oddiy qoida: "Stack bo'sh bo'lsa, Queue'dan ol, bajar". Lekin Queue ikkita — Microtask (yuqori prioritet) va Macrotask (past prioritet), va har bir Macrotask'dan keyin Microtask Queue to'liq bo'shatiladi — hatto bajarish jarayonida yangi qo'shilganlari ham. Brauzerda bunga rendering bosqichi ham qo'shiladi, shuning uchun setTimeout(fn, 0) hech qachon "darhol" degani emas. Microtask Queue V8'ning o'zida, ECMAScript spec darajasida joylashgan, Macrotask Queue va Web APIs/libuv esa — runtime'ning mas'uliyati.