Promise, async/await

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:

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.

Shuning uchun yuqoridagi misolda "kod tugadi" birinchi chiqadi: .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.

Ushbu misollar asinxron tartibni ko'rsatadi. Node.js va brauzerda ular kutilgan tartibda ishlaydi. Interaktiv maydonchada esa asinxron kodning natijasi darhol ko'rinmasligi mumkin โ€” shuning uchun bu misollar statik {code} sifatida berilgan.

Hodisalar tsikli (Event Loop) qisqacha

Yuqoridagilarni birlashtiruvchi mexanizm โ€” hodisalar tsikli (event loop). U cheksiz aylanuvchi jarayon bo'lib, quyidagicha ishlaydi:

  1. Makrovazifa navbatidan eng eski vazifani oladi va bajaradi (masalan, hozirgi skript);
  2. So'ng barcha mikrovazifalarni bajaradi (promise callback'lari);
  3. Kerak bo'lsa brauzer sahifani qayta render qiladi;
  4. 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.

Amaliy natija: agar biror kodni sinxron ishlar tugaganidan keyin, lekin brauzer render qilishidan oldin bajarmoqchi bo'lsangiz โ€” 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