Qo'shimcha brauzer mavzulari

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:

  1. Navbatdagi eng eski vazifa bor bo'lsa โ€” uni to'liq bajaradi;
  2. So'ng barcha to'plangan mikrovazifalarni bajaradi;
  3. Kerak bo'lsa render qiladi (ekranni yangilaydi);
  4. Vazifa yo'q bo'lsa โ€” yangi vazifa kelguncha kutadi;
  5. 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.

Agar biror vazifa juda uzoq davom etsa (masalan, og'ir tsikl), brauzer boshqa hech narsani bajara olmaydi: bosishlarga javob bermaydi, ekranni yangilamaydi. Foydalanuvchi "sahifa qotib qoldi" degan xabarni ko'radi. Shu bois og'ir hisob-kitobni bo'lish kerak (bu haqda quyida).

Makrovazifalar navbati

Makrovazifa (macrotask) โ€” event loop bajaradigan asosiy, yirik vazifa. Makrovazifalarga quyidagilar kiradi:

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:

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.

Ya'ni tartib shunday: bitta makrovazifa โ†’ butun mikrovazifalar navbati (bo'shatilguncha) โ†’ (kerak bo'lsa render) โ†’ keyingi makrovazifa. Mikrovazifalar makrovazifalardan oldin ustunlikka ega.

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. '1' va '4' โ€” bular joriy makrovazifa (skript) ichida sinxron bajariladi;
  2. '3' (promise) โ€” mikrovazifa. U skript tugagach, keyingi makrovazifadan oldin bajariladi;
  3. '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:

sinxron-tartib.js
// natija shu yerda chiqadi

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
Animatsiyalarni brauzer render tsikliga moslash uchun 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.

Ammo 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.

Qoida: foydalanuvchi interfeysini bloklamaslik kerak bo'lsa โ€” ishni makrovazifalar (setTimeout) orqali bo'ling, mikrovazifalar orqali emas. Mikrovazifalar renderni orqaga surmaydi, ular renderdan oldin turadi.
Zamonaviy usul โ€” haqiqatan og'ir hisob-kitobni Web Workerga chiqarish. U alohida oqimda (thread) ishlaydi va asosiy oqimni umuman bloklamaydi. 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