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>
â¦Lekin biz vazifa davomida ham biror narsa, masalan progress barni koârsatishni xohlashimiz mumkin.
Agar biz ogâir vazifani setTimeout yordamida boâlaklarga boâlsak, oâzgarishlar ular orasida boâyaladi.
Bu chiroyliroq koârinadi:
<div id="progress"></div>
<script>
let i = 0;
function count() {
// og'ir ishning bir qismini bajaring (*)
do {
i++;
progress.innerHTML = i;
} while (i % 1e3 != 0);
if (i < 1e7) {
setTimeout(count);
}
}
count();
</script>
Endi <div> i ning ortib borayotgan qiymatlarini koârsatadi, progress bar kabi.
Foydalanish holati 3: hodisadan keyin biror narsa qilish
Hodisa ishlov beruvchisida biz baâzi harakatlarni hodisa koâtarilgidan va barcha darajalarda boshqarilgandan keyin kechiktirishga qaror qilishimiz mumkin. Buni kodni nol kechikish bilan setTimeout ga oârash orqali qilishimiz mumkin.
Maxsus eventlarni jo'natish bobida misol koârdik: maxsus menu-open hodisasi setTimeout da joânatiladi, shunda u âclickâ hodisasi toâliq boshqarilgandan keyin sodir boâladi.
menu.onclick = function() {
// ...
// bosilgan menyu elementi ma'lumotlari bilan maxsus hodisa yaratish
let customEvent = new CustomEvent("menu-open", {
bubbles: true
});
// maxsus hodisani asinxron ravishda jo'natish
setTimeout(() => menu.dispatchEvent(customEvent));
};
Macrotasks va Microtasks
Bu bobda tasvirlangan macrotasks bilan bir qatorda, Mikrovazifalar va hodisalar tsikli bobida eslatib oâtilgan microtasks ham mavjud.
Microtasks faqat bizning kodimizdan keladi. Ular odatda promiselar tomonidan yaratiladi: .then/catch/finally ishlov beruvchisining bajarilishi microtask ga aylanadi. Microtasks await ning âqopqogâi ostidaâ ham ishlatiladi, chunki bu promise bilan ishlashning boshqa shakli.
Shuningdek, func ni microtask navbatida bajarish uchun navbatga qoâyadigan maxsus queueMicrotask(func) funksiyasi ham mavjud.
Har bir macrotask dan keyin darhol, dvigatel boshqa macrotasks yoki rendering yoki boshqa narsalarni ishga tushirishdan oldin microtask navbatidagi barcha vazifalarni bajaradi.
Masalan, qarang:
setTimeout(() => alert("timeout"));
Promise.resolve()
.then(() => alert("promise"));
alert("code");
Bu yerda tartib qanday boâladi?
codebirinchi koârsatiladi, chunki bu muntazam sinxron chaqiruv.promiseikkinchi koârsatiladi, chunki.thenmicrotask navbatidan oâtadi va joriy koddan keyin ishlaydi.timeoutoxirida koârsatiladi, chunki bu macrotask.
Boyroq event loop rasmi shunday koârinadi (tartib yuqoridan pastgacha, yaâni: avval skript, keyin microtasks, rendering va hokazo):
Barcha microtasks boshqa hodisalarni boshqarish yoki rendering yoki boshqa macrotask sodir boâlishidan oldin tugallanadi.
Bu muhim, chunki u microtasks orasida amaliyot muhiti asosan bir xil (sichqoncha koordinatalarining oâzgarishi yoâq, yangi tarmoq maâlumotlari yoâq va hokazo) ekanligini kafolatlaydi.
Agar biz funksiyani asinxron ravishda (joriy koddan keyin), lekin oâzgarishlar renderlangandan yoki yangi hodisalar boshqarilishidan oldin bajarishni xohlasak, uni queueMicrotask bilan rejalashtira olamiz.
Mana avval koârsatilganiga oâxshash âsanash progress bariâ bilan misol, lekin setTimeout oârniga queueMicrotask ishlatilgan. Uni eng oxirida render qilganini koârishingiz mumkin. Xuddi sinxron kod kabi:
<div id="progress"></div>
<script>
let i = 0;
function count() {
// og'ir ishning bir qismini bajaring (*)
do {
i++;
progress.innerHTML = i;
} while (i % 1e3 != 0);
if (i < 1e6) {
queueMicrotask(count);
}
}
count();
</script>
Xulosa
Yanada batafsil event loop algoritmi (spetsifikatsiya ga nisbatan hali ham soddalashtirilgan):
- Macrotask navbatidan eng eski vazifani navbatdan chiqarib ishga tushirish (masalan âscriptâ).
- Barcha microtasks ni bajarish:
- Microtask navbati boâsh boâlmagan vaqtda:
- Eng eski microtask ni navbatdan chiqarib ishga tushirish.
- Microtask navbati boâsh boâlmagan vaqtda:
- Agar biror oâzgarishlar boâlsa, ularni render qilish.
- Agar macrotask navbati boâsh boâlsa, macrotask paydo boâlguncha kutish.
- 1-qadamga oâtish.
Yangi macrotask rejalashtirish uchun:
- Nol kechikishli
setTimeout(f)dan foydalanish.
Bu katta hisob-kitob-ogâir vazifani boâlaklarga boâlish uchun ishlatilishi mumkin, brauzer ular orasida foydalanuvchi hodisalariga javob berish va progress koârsatish uchun.
Shuningdek, hodisa toâliq boshqarilgandan keyin (bubbling tugagandan keyin) harakatni rejalashtirish uchun hodisa ishlov beruvchilarida ishlatiladi.
Yangi microtask rejalashtirish uchun:
queueMicrotask(f)dan foydalanish.- Shuningdek promise ishlov beruvchilari microtask navbatidan oâtadi.
Microtasks orasida UI yoki tarmoq hodisalarini boshqarish yoâq: ular bir-biridan keyin darhol ishlaydi.
Shuning uchun funksiyani asinxron ravishda bajarish uchun, lekin muhit holatida queueMicrotask dan foydalanish mumkin.
Event loop ni bloklamasligi kerak boâlgan uzoq ogâir hisob-kitoblar uchun biz Web Workers dan foydalanishimiz mumkin.
Bu kodini boshqa, parallel thread da ishga tushirish usuli.
Web Workers asosiy jarayon bilan xabarlar almashishi mumkin, lekin ularda oâzlarining oâzgaruvchilari va oâzlarining event loop i bor.
Web Workers DOM ga kirish huquqiga ega emas, shuning uchun ular asosan bir vaqtning oâzida bir nechta CPU yadrolarini ishlatish uchun hisob-kitoblar uchun foydali.
Izohlar
<code>yorlig'ini ishlating, bir nechta satrlar uchun - ularni<pre>yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepenâ¦)