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:
- Call Stack — hozir bajarilayotgan kod shu yerda
- Web APIs — og'ir/kutish ishlari shu yerda (fon)
- 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 - microtaskbor → 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.