Mikrovazifalar (microtasks)
Promise'lar callback'lari (.then, .catch, .finally) doim asinxron bajariladi. Lekin qanday tartibda? Buni tushunish uchun mikrovazifa navbati (microtask queue) tushunchasini o'rganamiz.
Asinxron bajarilish tartibi
Quyidagi kodni ko'rib chiqamiz:
let promise = Promise.resolve();
promise.then(() => console.log('promise tayyor!'));
console.log('kod tugadi');
Sizningcha, natija qanday tartibda chiqadi? Ko'pchilik "promise tayyor!" birinchi bo'ladi deb o'ylaydi, chunki promise allaqachon resolve qilingan. Ammo natija bunday:
kod tugadi
promise tayyor!
Nima uchun? Chunki .then callback'i darhol emas, balki hozirgi kod to'liq tugagandan keyin ishga tushadi. Buning sababi โ mikrovazifa navbati.
Mikrovazifa navbati (Microtask Queue)
Asinxron vazifalar to'g'ri boshqarilishi kerak. ECMAScript standartida buning uchun ichki PromiseJobs navbati mavjud โ spetsifikatsiyada u "microtask queue" deb ataladi (V8 termini).
Navbat haqida asosiy tushunchalar:
- Navbat FIFO (First In, First Out) โ birinchi qo'shilgan vazifa birinchi bajariladi;
- Vazifalar bajarilishi faqat hech qanday boshqa kod ishlamayotgan paytda boshlanadi.
Oddiy qilib aytganda: promise tayyor (settled) bo'lganda, uning .then/.catch/.finally callback'lari navbatga qo'shiladi, lekin darhol bajarilmaydi. JavaScript dvigateli hozirgi kodni tugatib, so'ng navbatdagi vazifalarni birma-bir oladi va bajaradi.
.then callback'i navbatga tushdi, lekin faqat console.log('kod tugadi') bajarilib, asosiy kod tugagandan keyin navbatdan olindi.Agar callback'lar zanjiri bo'lsa, ular ham navbat orqali birma-bir bajariladi:
Promise.resolve()
.then(() => console.log('birinchi'))
.then(() => console.log('ikkinchi'))
.then(() => console.log('uchinchi'));
console.log('sinxron kod');
// Natija:
// sinxron kod
// birinchi
// ikkinchi
// uchinchi
Makrovazifalar va setTimeout
Mikrovazifalardan tashqari makrovazifalar (macrotasks) ham bor. setTimeout, tarmoq hodisalari, foydalanuvchi hodisalari โ bularning hammasi makrovazifalar navbatiga tushadi.
Muhim qoida: har bir makrovazifadan keyin dvigatel avval BARCHA mikrovazifalarni bajaradi, so'nggina keyingi makrovazifaga o'tadi (yoki qayta render qiladi). Ya'ni mikrovazifalar makrovazifalardan ustunroq.
setTimeout(() => console.log('setTimeout (makrovazifa)'), 0);
Promise.resolve()
.then(() => console.log('promise (mikrovazifa)'));
console.log('sinxron kod');
Natija quyidagicha bo'ladi:
sinxron kod
promise (mikrovazifa)
setTimeout (makrovazifa)
E'tibor bering: setTimeout(..., 0) "0 soniyadan keyin" degani bo'lsa-da, u makrovazifa bo'lgani uchun promise callback'idan keyin ishga tushdi. Chunki sinxron kod tugagach, dvigatel avval mikrovazifa navbatini (promise) to'liq bo'shatadi, keyingina makrovazifaga (setTimeout) o'tadi.
{code} sifatida berilgan.Hodisalar tsikli (Event Loop) qisqacha
Yuqoridagilarni birlashtiruvchi mexanizm โ hodisalar tsikli (event loop). U cheksiz aylanuvchi jarayon bo'lib, quyidagicha ishlaydi:
- Makrovazifa navbatidan eng eski vazifani oladi va bajaradi (masalan, hozirgi skript);
- So'ng barcha mikrovazifalarni bajaradi (promise callback'lari);
- Kerak bo'lsa brauzer sahifani qayta render qiladi;
- Agar makrovazifa navbatida yana vazifa bo'lsa โ 1-qadamga qaytadi; aks holda yangi hodisani kutadi.
Ushbu ketma-ketlik nima uchun promise callback'lari setTimeoutdan oldin ishlashini tushuntiradi: har bir makrovazifadan keyin mikrovazifa navbati to'liq bo'shatiladi.
Promise.resolve().then(...) ishlating (mikrovazifa). Agar renderdan keyin bo'lishini xohlasangiz โ setTimeout (makrovazifa).Unhandled rejection
Mikrovazifa navbati tushunchasi "ushlanmagan rejection" (unhandled rejection) qachon yuzaga kelishini ham tushuntiradi. Agar promise reject bo'lsa-yu, mikrovazifa navbati bo'shaganda unga .catch qo'shilmagan bo'lsa โ dvigatel "unhandled rejection" xatosini beradi.
let promise = Promise.reject(new Error('Xatolik!'));
// .catch qo'shilmadi
// Dvigatel navbat bo'shagach 'unhandled rejection' xabarini beradi
Ammo agar keyinroq (masalan setTimeout ichida) .catch qo'shsak, xato ushlanadi va "unhandled rejection" bo'lmaydi:
let promise = Promise.reject(new Error('Xatolik!'));
setTimeout(() => promise.catch(err => console.log('ushlandi')), 1000);
// Bu holda:
// Error: Xatolik! (avval, navbat bo'shagach)
// ushlandi (1 soniyadan keyin)
Xulosa
- Promise callback'lari (
.then/.catch/.finally) doim asinxron, mikrovazifa navbati orqali bajariladi; - Navbat FIFO tartibda ishlaydi va faqat hozirgi kod tugagach boshlanadi;
setTimeoutโ makrovazifa, u mikrovazifalardan keyin ishlaydi;- Har bir makrovazifadan so'ng dvigatel barcha mikrovazifalarni bajaradi;
- Bu mexanizm hodisalar tsikli (event loop) deb ataladi.