Brauzer JavaScript bajarilish oqimi, shuningdek Node.js da ham event loop ga asoslangan.
Event loop qanday ishlashini tushunish optimallashtirish uchun va ba’zan to’g’ri arxitektura uchun muhim.
Bu bobda avval narsalar qanday ishlashi haqidagi nazariy tafsilotlarni ko’rib chiqamiz, keyin esa bu bilimlarning amaliy qo’llanilishini ko’ramiz.
Event Loop
Event loop kontseptsiyasi juda oddiy. Cheksiz sikl mavjud bo’lib, JavaScript dvigateli vazifalarni kutadi, ularni bajaradi va keyin uyquga ketadi, ko’proq vazifalarni kutadi.
Dvigatelning umumiy algoritmi:
- Vazifalar mavjud bo’lganda:
- ularni bajaring, eng eski vazifadan boshlab.
- Vazifa paydo bo’lguncha uyqu, keyin 1-qadamga o’ting.
Bu sahifani ko’rib chiqayotganda ko’rgan narsalarimizning rasmiylashtirishidir. JavaScript dvigateli ko’p vaqt hech narsa qilmaydi, u faqat skript/ishlov beruvchi/hodisa faollashgandagina ishlaydi.
Vazifalar misollari:
- Tashqi skript
<script src="...">yuklanganda, vazifa uni bajarishdir. - Foydalanuvchi sichqonchasini harakatlantirganda, vazifa
mousemovehodisasini jo’natish va ishlov beruvchilarni bajarishdir. - Rejalashtirilgan
setTimeoutvaqti kelganda, vazifa uning callback ni ishga tushirishdir. - …va hokazo.
Vazifalar o’rnatiladi – dvigatel ularni boshqaradi – keyin ko’proq vazifalarni kutadi (uyquda va deyarli nol CPU iste’mol qiladi).
Dvigatel band bo’lganida vazifa kelishi mumkin, u holda u navbatga qo’yiladi.
Vazifalar navbat hosil qiladi, “macrotask queue” deb ataladi (v8 terminologiyasi):
Masalan, dvigatel script ni bajarayotgan vaqtda foydalanuvchi sichqonchasini harakatlantirib mousemove ga sabab bo’lishi va setTimeout vaqti kelishi mumkin va hokazo, bu vazifalar yuqoridagi rasmda ko’rsatilganidek navbat hosil qiladi.
Navbatdagi vazifalar “birinchi kelgan – birinchi xizmat ko’rsatilgan” asosida qayta ishlanadi. Brauzer dvigateli script bilan ishini tugatganda, u mousemove hodisasini, keyin setTimeout ishlov beruvchisini va hokazolarni boshqaradi.
Hozirgacha, juda oddiy, shunday emasmi?
Yana ikkita tafsilot:
- Dvigatel vazifani bajarayotgan vaqtda render hech qachon sodir bo’lmaydi. Vazifa qanchalik uzoq davom etishidan qat’i nazar. DOM ga o’zgarishlar faqat vazifa tugagandan keyin bo’yaladi.
- Agar vazifa juda uzoq davom etsa, brauzer foydalanuvchi hodisalarini qayta ishlash kabi boshqa vazifalarni bajara olmaydi. Shuning uchun bir muncha vaqtdan keyin u “Page Unresponsive” kabi ogohlantirish beradi va vazifani butun sahifa bilan birga o’ldirishni taklif qiladi. Bu juda ko’p murakkab hisob-kitoblar yoki cheksiz siklga olib keladigan dasturlash xatosi bo’lganda sodir bo’ladi.
Bu nazariya edi. Endi bu bilimni qanday qo’llashimizni ko’raylik.
Foydalanish holati 1: CPU-och vazifalarni bo’lish
Aytaylik, bizda CPU-och vazifa bor.
Masalan, sintaksis-ta’kidlash (bu sahifadagi kod misollarini ranglash uchun ishlatiladi) CPU uchun juda og’ir. Kodni ta’kidlash uchun u tahlil qiladi, ko’plab rangli elementlar yaratadi, ularni hujjatga qo’shadi – katta miqdordagi matn uchun bu juda ko’p vaqt talab qiladi.
Dvigatel sintaksis ta’kidlash bilan band bo’lganida, u boshqa DOM bilan bog’liq ishlarni bajara olmaydi, foydalanuvchi hodisalarini qayta ishlay olmaydi va hokazo. Bu hatto brauzerning biroz “hiqichoq” yoki “osilib qolishiga” sabab bo’lishi mumkin, bu qabul qilinishi mumkin emas.
Katta vazifani bo’laklarga bo’lish orqali muammolardan qochishimiz mumkin. Dastlabki 100 qatorni ta’kidlaymiz, keyin keyingi 100 qator uchun setTimeout (nol kechikish bilan) rejalashtiramiz va hokazo.
Bu yondashuvni namoyish qilish uchun, soddalik uchun, matn ta’kidlash o’rniga 1 dan 1000000000 gacha sanash funksiyasini olaylik.
Agar quyidagi kodni ishga tushirsangiz, dvigatel bir muncha vaqt “osilib qoladi”. Server tomonidagi JS uchun bu aniq seziladi va agar uni brauzerde ishga tushirayotgan bo’lsangiz, sahifadagi boshqa tugmalarni bosishga harakat qiling – sanash tugamaguncha boshqa hodisalar boshqarilmasligini ko’rasiz.
let i = 0;
let start = Date.now();
function count() {
// og'ir ishni bajaring
for (let j = 0; j < 1e9; j++) {
i++;
}
alert("Done in " + (Date.now() - start) + 'ms');
}
count();
Brauzer hatto “skript juda uzoq vaqt ishlayapti” ogohlantirishini ham ko’rsatishi mumkin.
Ichki setTimeout chaqiruvlari yordamida ishni bo’laylik:
let i = 0;
let start = Date.now();
function count() {
// og'ir ishning bir qismini bajaring (*)
do {
i++;
} while (i % 1e6 != 0);
if (i == 1e9) {
alert("Done in " + (Date.now() - start) + 'ms');
} else {
setTimeout(count); // yangi chaqiruvni rejalashtiring (**)
}
}
count();
Endi brauzer interfeysi “sanash” jarayonida to’liq funktsional.
count ning bitta ishga tushishi ishning bir qismini bajaradi (*) va keyin kerak bo’lsa o’zini qayta rejalashtiradi (**):
- Birinchi ishga tushish sanaydi:
i=1...1000000. - Ikkinchi ishga tushish sanaydi:
i=1000001..2000000. - …va hokazo.
Endi, agar dvigatel 1-qismni bajarayotgan vaqtda yangi yon vazifa (masalan onclick hodisasi) paydo bo’lsa, u navbatga qo’yiladi va keyin 1-qism tugagandan so’ng, keyingi qismdan oldin bajariladi. count bajarilishlari orasidagi davriy event loop ga qaytishlar JavaScript dvigateli uchun boshqa ish qilish, boshqa foydalanuvchi harakatlariga javob berish uchun etarli “havo” beradi.
E’tiborli narsa shundaki, ikkala variant – setTimeout bilan ishni bo’lish va bo’lmaslik – tezlikda taqqoslanadigan. Umumiy sanash vaqtida unchalik katta farq yo’q.
Ularni yaqinroq qilish uchun yaxshilash qilaylik.
Rejalashtirishni count() ning boshiga ko’chiramiz:
let i = 0;
let start = Date.now();
function count() {
// rejalashtirishni boshiga ko'chiring
if (i < 1e9 - 1e6) {
setTimeout(count); // yangi chaqiruvni rejalashtiring
}
do {
i++;
} while (i % 1e6 != 0);
if (i == 1e9) {
alert("Done in " + (Date.now() - start) + 'ms');
}
}
count();
Endi count() ni boshlashda va ko’proq count() qilishimiz kerakligini ko’rganimizda, biz buni ishni qilishdan oldin darhol rejalashtiramiz.
Agar uni ishga tushirsangiz, sezilarli darajada kamroq vaqt ketishini osongina sezasiz.
Nega?
Bu oddiy: eslab qolganingizdek, ko’plab ichki setTimeout chaqiruvlari uchun brauzerdagi minimal kechikish 4ms. 0 o’rnatsak ham, bu 4ms (yoki biroz ko’proq). Shuning uchun uni qanchalik erta rejalashtarsak – shunchalik tez ishlaydi.
Nihoyat, biz CPU-och vazifani qismlarga bo’ldik – endi u foydalanuvchi interfeysini bloklamaydi. Va uning umumiy bajarilish vaqti unchalik uzoqroq emas.
Foydalanish holati 2: progress ko’rsatish
Brauzer skriptlari uchun og’ir vazifalarni bo’lishning yana bir foydasi shundaki, biz progress ko’rsatishini ko’rsatishimiz mumkin.
Yuqorida aytilganidek, DOM ga o’zgarishlar faqat hozirda ishlaydigan vazifa tugagandan keyin bo’yaladi, qanchalik uzoq davom etishidan qat’i nazar.
Bir tomondan, bu ajoyib, chunki bizning funksiyamiz ko’plab elementlar yaratishi, ularni hujjatga birma-bir qo’shishi va ularning stillarini o’zgartirishi mumkin – tashrif buyuruvchi hech qanday “oraliq”, tugallanmagan holatni ko’rmaydi. Muhim narsa, shunday emasmi?
Mana demo, i ga o’zgarishlar funksiya tugamaguncha ko’rsatilmaydi, shuning uchun biz faqat oxirgi qiymatni ko’ramiz:
<div id="progress"></div>
<script>
function count() {
for (let i = 0; i < 1e6; i++) {
i++;
progress.innerHTML = i;
}
}
count();
</script>