Hodisalar tsikli: mikro va makrovazifalar
JavaScript'ning bajarilish tartibini tushunish uchun eng muhim tushuncha โ hodisalar tsikli (event loop). U brauzer (yoki Node.js) kelib tushgan vazifalarni qanday navbatga qo'yishini, qaysi biri oldin bajarilishini va render qachon bo'lishini boshqaradi. Ushbu darsda makrovazifa va mikrovazifa navbatlarini, ularning tartibini va amaliy oqibatlarini chuqur ko'rib chiqamiz.
Hodisalar tsikli tushunchasi
JavaScript dvigateli (engine) juda oddiy tamoyil asosida ishlaydi: bajariladigan vazifa bo'lsa โ bajaradi, bo'lmasa โ kutadi. Bu cheksiz aylanuvchi algoritm quyidagicha ta'riflanadi:
- Navbatdagi eng eski vazifa bor bo'lsa โ uni to'liq bajaradi;
- So'ng barcha to'plangan mikrovazifalarni bajaradi;
- Kerak bo'lsa render qiladi (ekranni yangilaydi);
- Vazifa yo'q bo'lsa โ yangi vazifa kelguncha kutadi;
- Yangi vazifa kelsa โ 1-qadamdan boshlab qaytaradi.
Bu yerda kalit tushuncha shuki, JavaScript bir oqimli (single-threaded): bir vaqtning o'zida faqat bitta vazifa bajariladi. Vazifa boshlanganda, u tugamaguncha boshqa hech narsa (hatto foydalanuvchi bosishlari ham) qayta ishlanmaydi.
Makrovazifalar navbati
Makrovazifa (macrotask) โ event loop bajaradigan asosiy, yirik vazifa. Makrovazifalarga quyidagilar kiradi:
- Skriptning o'zi (dastlabki kodni ishga tushirish);
setTimeoutvasetIntervalchaqiruvlari;- Foydalanuvchi hodisalarini qayta ishlash (
click,keydownva h.k.); - Tarmoq so'rovi tugaganda ishlaydigan kod (masalan, hodisa sifatida).
Makrovazifalar navbatda (queue) turadi va event loop ularni birma-bir, kelgan tartibida bajaradi (FIFO โ First In, First Out).
Mikrovazifalar navbati
Mikrovazifa (microtask) โ asosan Promiselardan kelib chiqadigan mayda vazifa. Mikrovazifalar quyidagilar orqali hosil bo'ladi:
.then,.catch,.finallyishlovchilari;async/awaitichidaawaitdan keyingi kod;queueMicrotask(fn)โ mikrovazifani qo'lda navbatga qo'yish.
Eng muhim qoida: har bir makrovazifa tugagach, event loop navbatdagi keyingi makrovazifaga o'tishdan oldin BARCHA to'plangan mikrovazifalarni bajaradi. Agar mikrovazifalar ichida yana yangi mikrovazifalar paydo bo'lsa, ular ham darhol, shu yerda bajariladi.
Tartibga oid misol
Quyidagi kod nima chiqaradi? Bu klassik savol. Kodni o'ylab ko'ring:
console.log('1: skript boshi');
setTimeout(() => console.log('2: setTimeout'), 0);
Promise.resolve().then(() => console.log('3: promise'));
console.log('4: skript oxiri');
Natija tartibi:
1: skript boshi
4: skript oxiri
3: promise
2: setTimeout
Sababi:
'1'va'4'โ bular joriy makrovazifa (skript) ichida sinxron bajariladi;'3'(promise) โ mikrovazifa. U skript tugagach, keyingi makrovazifadan oldin bajariladi;'2'(setTimeout) โ yangi makrovazifa, u navbatning oxirida, mikrovazifalardan keyin bajariladi.
setTimeout(fn, 0) "darhol bajar" degani emas โ u "joriy makrovazifa va barcha mikrovazifalar tugagach, keyingi makrovazifa sifatida bajar" degani. Shuning uchun Promise.then undan doim oldin ishlaydi.Faqat sinxron tartibli playground misol
Quyidagi misolda hech qanday kechikish yo'q โ barchasi sinxron, shu bois tartib aniq va tekshirsa bo'ladi. Bu event loop emas, balki oddiy chaqiruvlar steki (call stack) tartibi:
Chiqish: 1, 2, 3, 4 โ chunki funksiya chaqiruvlari steki sinxron ishlaydi; hech narsa navbatga qo'yilmaydi.
queueMicrotask bilan tartib
Sof mikrovazifa tartibini queueMicrotask orqali ham ko'rsatish mumkin. Mikrovazifa hozirgi sinxron kod tugagach ishlaydi:
console.log('A: boshi');
queueMicrotask(() => console.log('C: mikrovazifa'));
console.log('B: oxiri');
// Chiqish: A, B, C
Ya'ni queueMicrotask kodni "joriy sinxron blok tugagandan keyin, lekin har qanday setTimeoutdan oldin" bajarilishga qo'yadi.
Render โ ekran qachon yangilanadi
Brauzer ekranni har bir makrovazifadan keyin (va barcha mikrovazifalar tugagach) yangilashi mumkin. Lekin muhimi shundaki, render hech qachon vazifa o'rtasida bo'lmaydi. Agar vazifa uzoq davom etsa, uning ichida DOMni necha marta o'zgartirsangiz ham, foydalanuvchi faqat vazifa tugagach oxirgi holatni ko'radi.
// bu kod ekranda faqat oxirgi rangni ko'rsatadi
elem.style.color = 'red';
elem.style.color = 'green';
elem.style.color = 'blue';
// render faqat kod tugagach bo'ladi -> ko'k rang ko'rinadi
requestAnimationFrame(fn) ishlatiladi โ u fnni keyingi render'dan oldin chaqiradi. Bu setTimeoutdan aniqroq va silliqroq animatsiya beradi.Og'ir hisob-kitobni bo'lish: setTimeout usuli
Faraz qilaylik, bizda juda katta hisob-kitob bor โ u bir necha soniya davom etadi. Uni bitta makrovazifada bajarsak, brauzer shu vaqt davomida qotib qoladi. Yechim โ ishni kichik bo'laklarga bo'lib, har bir bo'lakni setTimeout orqali alohida makrovazifada bajarish. Bu boshqa hodisalarga (bosishlar, render) "nafas olish" imkonini beradi:
let i = 0;
let natija = 0;
function hisobla() {
do {
i++;
natija += i;
} while (i % 1000000 !== 0); // har safar 1 mln qadam
if (i < 1000000000) {
setTimeout(hisobla, 0); // navbatning oxirida davom etamiz
} else {
console.log('Tayyor: ' + natija);
}
}
hisobla();
Bu yerda har setTimeout(hisobla, 0) chaqiruvi orasida event loop boshqa makrovazifalarni (foydalanuvchi hodisalari, render) bajarishga ulguradi. Natijada brauzer "tirik" qoladi.
setTimeout(fn, 0) tez-tez chaqirilsa ham, brauzer minimal kechikishni (odatda joylashish darajasi 5 dan oshsa ~4 ms) qo'shadi. Shuning uchun ko'p kichik bo'laklar sekinlashuvga olib kelishi mumkin. Shu bois har bo'lakni juda kichik qilmang โ masalan, yuqoridagi kabi katta blok bilan ishlang.Og'ir hisob-kitob va mikrovazifa farqi
Muhim: og'ir ishni Promise.then yoki queueMicrotask orqali bo'lish foyda bermaydi. Chunki mikrovazifalar joriy makrovazifadan keyin, lekin render'dan oldin bajariladi. Agar siz ishni mikrovazifalar orqali bo'lsangiz, brauzer ular hammasi tugamaguncha renderga o'ta olmaydi โ sahifa baribir qotib qoladi.
setTimeout) orqali bo'ling, mikrovazifalar orqali emas. Mikrovazifalar renderni orqaga surmaydi, ular renderdan oldin turadi.setTimeout bilan bo'lish esa sof JavaScript darajasidagi yechim.Amaliy foyda: progressni ko'rsatish
Ishni setTimeout bilan bo'lishning yana bir foydasi โ jarayon davomida progressni yangilash. Har bir bo'lakdan keyin render bo'lgani uchun, foydalanuvchi "progress bar"ning harakatini real vaqtda ko'radi. Agar hammasi bitta makrovazifada bo'lganida, faqat oxirida to'satdan 100% ko'rinardi.
Xulosa
- Event loop โ vazifalarni birma-bir bajaruvchi cheksiz tsikl; JavaScript bir oqimli;
- Makrovazifalar: skript,
setTimeout/setInterval, hodisalar. Navbatda birma-bir bajariladi; - Mikrovazifalar:
Promise.then/catch/finally,awaitdan keyingi kod,queueMicrotask; - Tartib: bitta makrovazifa โ barcha mikrovazifalar โ (render) โ keyingi makrovazifa. Mikrovazifalar ustunlikka ega;
setTimeout(fn, 0)โ "darhol" emas, "navbatning oxirida keyingi makrovazifa sifatida" degani;- Render vazifa o'rtasida bo'lmaydi โ faqat vazifa tugagach ekran yangilanadi;
- Og'ir hisob-kitobni interfeysni bloklamasdan bajarish uchun uni
setTimeoutorqali makrovazifalarga bo'ling (mikrovazifalarga emas), yoki Web Worker'dan foydalaning.