Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. Ikki navbat: kim qayerga tushadi
- 2.1 Task va microtask manbalari
- 2.2 Bitta muhim aniqlik
- 3. Zanjirlar qanday navbatga turadi
- 3.1 Ikki zanjir almashadi
- 3.2 then ichidan Promise qaytarish — qo'shimcha qadamlar
- 4. await qadamlari
- 4.1 await — funksiyaning davomi microtask bo'ladi
- 4.2 Intervyu masalasini yechamiz
- 4.3 return va return await
- 5. Microtask ochligi
- 5.1 await li sikl taymerni to'xtatadi
- 5.2 Tuzatish: task orqali bo'shatish
- 5.3 Brauzerda: chizish ham kutadi
- 6. Brauzerda: tinglovchilar orasidagi microtask'lar
- 7. Node'da nimasi boshqacha
- 8. Ko'p uchraydigan xatolar
- 8.1 "await keyingi task'ga o'tkazadi" deb o'ylash
- 8.2 Microtask tafsilotlariga tayanish
- 8.3 try ichida await siz return
- 8.4 Microtask'da xatoni yo'qotish
- 9. Mashqlar
- 1-mashq (oson): S, M yoki T?
- 2-mashq (o'rta): Zanjir va async aralash
- 3-mashq (qiyin): Chizishni bir marta rejalashtirish
- 10. Real ishda
- Xulosa
- Manbalar
Microtask va macrotask chuqur: then, await va bajarilish tartibi
Qisqacha: Promise hal bo'lganda uning
thencallback'lari microtask navbatiga tushadi, zanjirdagi keyingithenesa faqat oldingisi tugagach.awaitfunksiyani to'xtatadi va uning davomini microtask qilib qo'yadi — keyingi task'ga emas. Shuning uchunawaittaymerlarga ham, ekranni chizishga ham yo'l bermaydi: microtask'lar zanjiri tugamaguncha task navbati kutadi. Bu microtask ochligi (starvation) deyiladi.
Bu darsda
thencallback'i aynan qachon microtask navbatiga tushishini va ikki zanjir nega "almashib" ishlashini tushuntira olasiz.awaitning qadamlarini bilasiz va intervyudagiasync+setTimeout+Promisearalash masalalarni qog'ozda yecha olasiz.returnvareturn awaitfarqini — tartibda ham,try/catchda ham — bilasiz.- Microtask ochligini taniysiz:
awaitli sikl nega taymer va chizishni to'xtatib qo'yishini va qanday tuzatishni. - Brauzerda tinglovchilar orasida microtask'lar qachon bajarilishini bilasiz.
Oldin bilishingiz kerak: Event loop, Promise zanjiri, async / await, Promise yordamchilari va promisify.
1. Nega bu kerak?
Frontend intervyusida shu kodni berib, "konsolga nima, qaysi tartibda chiqadi?" deb so'rashadi:
async function birinchi() {
console.log("birinchi: boshlandi");
await ikkinchi();
console.log("birinchi: tugadi");
}
async function ikkinchi() {
console.log("ikkinchi");
}
console.log("skript boshlandi");
setTimeout(() => console.log("taymer"), 0);
birinchi();
new Promise((resolve) => {
console.log("executor");
resolve();
}).then(() => console.log("then"));
console.log("skript tugadi");Hozir javobni ochmang. Qog'ozga sakkiz qatorni o'zingiz o'ylagan tartibda yozing. Darsning o'rtasida shu kodni qadamma-qadam yechamiz va solishtirasiz.
Event loop darsida asosiy qoidani olgan edik: sinxron kod → hamma microtask'lar → bitta task → hamma microtask'lar → .... O'shanda microtask'ni faqat queueMicrotask bilan yaratgan edik va "Promise callback'lari ham microtask" degan edik. Bugun aniq javob beramiz: then va await qaysi paytda microtask yaratadi. Shu bilan har qanday tartib masalasi yechiladi.
Bu bilim faqat intervyu uchun emas. "Nega yuklanish belgisi ko'rinmadi?", "Nega taymer kech ishladi?", "Nega catch xatoni ushlamadi?" — real xatolarning ko'pi shu yerdan chiqadi.
2. Ikki navbat: kim qayerga tushadi
2.1 Task va microtask manbalari
Event loop darsidagi task ni ko'p maqolalarda macrotask deb atashadi — "katta ish", microtask'ga qarama-qarshi qo'yish uchun. HTML standartida rasmiy nomi — task. Ikkalasi bir narsa.
| Task (macrotask) navbatiga | Microtask navbatiga |
|---|---|
| skriptning o'zi | then, catch, finally callback'lari |
setTimeout, setInterval callback'lari |
await dan keyingi davom |
| bosish, klaviatura va boshqa hodisalar | queueMicrotask(fn) |
| tarmoq va fayl javoblari | MutationObserver callback'lari |
Node: setImmediate |
Node: process.nextTick (alohida navbat, tartibi — Event loop darsida) |
MutationObserver — DOM o'zgarishini kuzatuvchi, 13-qismda ko'ramiz; hozir bilish shart emas. Bugun asosan ikkinchi ustunning birinchi ikki qatori bilan ishlaymiz.
2.2 Bitta muhim aniqlik
Promise callback'lari microtask navbatiga Promise hal bo'lganda tushadi, then yozilganda emas. Ikki holat bor:
thenulanganda Promise allaqachon hal bo'lgan — callback shu zahoti navbatga qo'yiladi.- Promise hali kutilmoqda — callback Promise'da "eslab qolinadi" va
resolve/rejectchaqirilganda navbatga tushadi.
const chek = new Promise((resolve) => {
setTimeout(() => {
console.log("2. taymer: resolve chaqirildi");
resolve("osh");
console.log("3. taymer: resolve dan keyingi qator");
}, 100);
});
chek.then((taom) => console.log("4. then:", taom));
console.log("1. sinxron kod tugadi");Konsolda:
1. sinxron kod tugadi
2. taymer: resolve chaqirildi
3. taymer: resolve dan keyingi qator
4. then: oshthen callback'i 100 ms davomida Promise ichida kutdi. resolve("osh") uni microtask navbatiga qo'ydi — lekin darhol bajarmadi. Taymer callback'i (task) oxirigacha ishladi, "3" ni chiqardi. Faqat task tugab, stek bo'shagach — microtask "4".
Tekshirib ko'ring:
resolvechaqirilgan qatordathencallback'i bajariladimi?
Javob
Yo'q. resolve faqat callback'ni microtask navbatiga qo'yadi. Callback joriy kod (bu yerda taymer callback'i) tugab, stek bo'shagandan keyin bajariladi.
3. Zanjirlar qanday navbatga turadi
3.1 Ikki zanjir almashadi
Zanjirdagi har then yangi Promise qaytaradi (Promise zanjiri). Keyingi then o'sha yangi Promise'ga ulangan. U faqat oldingi callback bajarilib bo'lgach hal bo'ladi. Demak zanjirning ikkinchi bo'g'ini birinchisi bilan birga navbatga tushmaydi — birinchisi bajarilgandan keyin tushadi.
Ikki zanjirni yonma-yon qo'ysak:
Promise.resolve()
.then(() => console.log("A1"))
.then(() => console.log("A2"))
.then(() => console.log("A3"));
Promise.resolve()
.then(() => console.log("B1"))
.then(() => console.log("B2"));Konsolda:
A1
B1
A2
B2
A3Ko'pchilik A1 A2 A3 B1 B2 ni kutadi — "birinchi zanjir to'liq, keyin ikkinchisi". Lekin zanjirlar almashib ishladi. Qadamlarda qarang — microtask navbatida bir paytda har zanjirdan faqat bitta bo'g'in bor:
Bu hammasi bitta task ichida, taymerlarsiz bo'ldi. Zanjir bo'g'inlari — alohida-alohida microtask'lar.
3.2 then ichidan Promise qaytarish — qo'shimcha qadamlar
Endi qiyinroq holat. then callback'i oddiy qiymat emas, Promise qaytarsa:
Promise.resolve()
.then(() => {
console.log(0);
return Promise.resolve(4);
})
.then((son) => console.log(son));
Promise.resolve()
.then(() => console.log(1))
.then(() => console.log(2))
.then(() => console.log(3))
.then(() => console.log(5))
.then(() => console.log(6));Konsolda:
0
1
2
3
4
5
64 — 0 dan keyin darhol emas, uch "qadam" keyin chiqdi. Sabab: then callback'i Promise qaytarsa, zanjir o'sha Promise'ni "ochishi" kerak — ichidagi natijani olishi kerak. Xuddi sovg'a qutisi kabi: qo'lingizga quti berishsa, sovg'ani olish uchun avval qutini ochasiz.
Standart bo'yicha bu ochish ikki qo'shimcha microtask oladi. Birinchisi qaytarilgan Promise'ga then ulaydi. Ikkinchisi natijani zanjirga uzatadi. Qadamlarda qarang — «ochish» bo'g'inlari navbatda 1, 2, 3 bilan almashib turadi:
Oddiy qiymat (return 4) qaytarilsa, ochish kerak emas — 4 ham 2 bilan bir qadamda chiqardi.
Bu — intervyuda "eng qiyin" deb beriladigan masala. Real kodda esa bunday tafsilotga tayanmang: ikki mustaqil zanjir orasidagi tartib muhim bo'lsa, uni aniq qiling — bitta zanjirga yoki ketma-ket await ga yig'ing. Bu tafsilotni bilish kerak, chunki u "nega mana bu yozuv kutilmagan joyda chiqdi?" degan savolga javob beradi.
Tekshirib ko'ring: Yuqoridagi kodda
return Promise.resolve(4)o'rnigareturn 4yozilsa,4qaysi raqamlar orasida chiqadi?
Javob
1 va 2 orasida: 0, 1, 4, 2, 3, 5, 6. Oddiy qiymat qaytgani uchun keyingi bo'g'in 0 bajarilishi bilan navbatga tushadi — 1 dan keyin. Promise'ni ochish uchun ikki qo'shimcha qadam kerak emas.
4. await qadamlari
4.1 await — funksiyaning davomi microtask bo'ladi
async funksiya chaqirilganda u birinchi await gacha sinxron ishlaydi — xuddi oddiy funksiya kabi. await ga yetganda:
- funksiya to'xtaydi va chaqiruvchiga Promise qaytaradi;
- kutilayotgan qiymat hal bo'lganda funksiyaning davomi microtask navbatiga tushadi.
await dan keyingi kod — then callback'ining o'zi, faqat chiroyli yozilgan. Hatto await null — kutadigan hech narsa yo'q — ham funksiyani to'xtatib, davomini microtask qiladi:
async function oshpaz() {
console.log("oshpaz: 1");
await null;
console.log("oshpaz: 2");
await null;
console.log("oshpaz: 3");
}
oshpaz();
Promise.resolve()
.then(() => console.log("ofitsiant: 1"))
.then(() => console.log("ofitsiant: 2"));
console.log("sinxron kod tugadi");Konsolda:
oshpaz: 1
sinxron kod tugadi
oshpaz: 2
ofitsiant: 1
oshpaz: 3
ofitsiant: 2"oshpaz: 1" — sinxron, chaqiruv paytida. Birinchi await da oshpaz to'xtadi va davomi navbatga tushdi — then zanjiridan oldin, chunki u oldin chaqirilgan. Keyin ular almashib ishlaydi — «Ikki zanjir almashadi» dagi kabi. async funksiya — await lar bilan bo'lingan then zanjiri.
4.2 Intervyu masalasini yechamiz
Endi «Nega bu kerak?» dagi kodga qaytamiz. Qog'ozingizni oling. Event loop dagi to'rt qadamli usulni eslaymiz:
- Sinxron qatorlar — kod tartibida, hammasidan oldin.
- Har callback qaysi navbatga tushadi:
thenvaawaitdavomi — microtask,setTimeout— task. - Skript tugagach — microtask navbati oxirigacha.
- Keyin bitta task, va yana 3-qadam.
Uchta tuzoqqa e'tibor bering:
await ikkinchi()—ikkinchidarhol chaqiriladi va "ikkinchi" sinxron chiqadi.awaitfaqat uning natijasini kutadi.- Executor sinxron ishlaydi (Promise asoslari) — "executor" ham "skript tugadi" dan oldin.
- Taymer — 0 ms bo'lsa ham eng oxirida: u task, qolganlar esa microtask yoki sinxron.
4.3 return va return await
async funksiya Promise qaytarsa, natija chaqiruvchiga qancha qadamda yetadi? Uch variantni bir vaqtda ishga tushiramiz va yonida "qadam o'lchagich" zanjirini qo'yamiz:
const chek = Promise.resolve("osh");
async function qiymat() {
return "osh";
}
async function awaitBilan() {
return await chek;
}
async function awaitsiz() {
return chek;
}
awaitsiz().then(() => console.log("return chek"));
awaitBilan().then(() => console.log("return await chek"));
qiymat().then(() => console.log("return qiymat"));
Promise.resolve()
.then(() => console.log("1-qadam"))
.then(() => console.log("2-qadam"))
.then(() => console.log("3-qadam"));Konsolda:
return qiymat
1-qadam
return await chek
2-qadam
return chek
3-qadamOddiy qiymat — bir qadamda. return await — ikki qadamda: biri await uchun, biri natijani qaytarish uchun. await siz return chek esa eng sekin — uch qadam. Sabab «then ichidan Promise qaytarish» dagi bilan bir xil: async funksiya qaytarilgan Promise'ni "ochishi" kerak va bu ikki qo'shimcha qadam oladi.
Bir necha microtask — nanosekundlar, tezlik uchun ahamiyatsiz. Muhimi boshqa: Promise kombinatorlari darsining 2-mashqida va'da bergan tuzoq — try/catch ichida await siz return:
function tayyorla(taom, ms, xato) {
return new Promise((resolve, reject) => {
setTimeout(() => {
xato ? reject(new Error(xato)) : resolve(taom);
}, ms);
});
}
async function awaitBilan() {
try {
return await tayyorla("osh", 100, "Guruch tugadi");
} catch (xato) {
return "zaxira: somsa";
}
}
async function awaitsiz() {
try {
return tayyorla("osh", 100, "Guruch tugadi");
} catch (xato) {
return "zaxira: somsa";
}
}
async function solishtir() {
console.log(await awaitBilan());
try {
console.log(await awaitsiz());
} catch (xato) {
console.log("Ushlanmadi:", xato.message);
}
}
solishtir();Konsolda:
zaxira: somsa
Ushlanmadi: Guruch tugadiBirinchi funksiyada hammasi to'g'ri. Ikkinchisida — awaitsiz da — return tayyorla(...) Promise'ni darhol qaytardi va try bloki shu zahoti tugadi. Promise esa 100 ms dan keyin rad etildi — try allaqachon yo'q edi. Shuning uchun catch ishlamadi va xato chaqiruvchiga o'tib ketdi. Funksiya return await bilan esa try ichida kutadi va rad etilish catch ga tushadi.
Qoida: try ichida Promise qaytarsangiz — doim return await. try dan tashqarida farqi yo'q, ikkalasi ham ishlaydi.
Tekshirib ko'ring:
async function f() { await g(); console.log("f"); }—goddiy (asyncbo'lmagan) funksiya bo'lib,undefinedqaytarsa, "f" sinxron chiqadimi?
Javob
Yo'q. await undefined ham funksiyani to'xtatadi va davomini microtask navbatiga qo'yadi — xuddi await null kabi. "f" joriy sinxron kod tugagach chiqadi.
5. Microtask ochligi
5.1 await li sikl taymerni to'xtatadi
Event loop darsida queueMicrotask ning cheksiz zanjiri taymerni "och qoldirgan" edi. await bilan ham aynan shunday bo'ladi — faqat buni sezish qiyinroq, chunki kod bexavotir ko'rinadi:
setTimeout(() => console.log("Taymer: yangi buyurtma!"), 0);
async function chekniSana() {
let soni = 0;
while (soni < 100_000) {
soni++;
await null;
}
console.log("Cheklar sanaldi:", soni);
}
chekniSana();Konsolda:
Cheklar sanaldi: 100000
Taymer: yangi buyurtma!Sikl 100 000 marta await qildi — 100 000 marta "to'xtadi". Lekin har await ning davomi microtask. Microtask navbati esa bo'shamaguncha task navbatiga — taymerga — navbat kelmaydi. 0 ms li taymer butun sikl tugashini kutdi.
Bu holat microtask ochligi (microtask starvation) deyiladi: microtask'lar bir-birini to'xtovsiz qo'yib, task'larni "och" qoldiradi. Sikl cheksiz bo'lsa, taymer hech qachon ishlamaydi.
5.2 Tuzatish: task orqali bo'shatish
Navbatni haqiqatan bo'shatish uchun davomni task qilish kerak — masalan, setTimeout bilan. Promise yordamchilari darsidagi kut ni 0 ms bilan chaqirsak — kut(0) — aynan shuni qiladi:
function kut(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
setTimeout(() => console.log("Taymer: yangi buyurtma!"), 0);
async function chekniSana() {
for (let bolak = 1; bolak <= 3; bolak++) {
console.log(`${bolak}-bo'lak sanaldi`);
await kut(0);
}
}
chekniSana();Konsolda:
1-bo'lak sanaldi
Taymer: yangi buyurtma!
2-bo'lak sanaldi
3-bo'lak sanaldiawait kut(0) dagi davom endi taymer callback'i — task — orqali keladi. Ular orasida event loop boshqa task'larni ham bajaradi: "yangi buyurtma" birinchi bo'lakdan keyin o'tdi. Bu — Event loop darsidagi bo'laklash, async/await shaklida.
5.3 Brauzerda: chizish ham kutadi
Microtask'lar chizishdan ham oldin bajariladi. Demak await null li sikl paytida ekran yangilanmaydi. Ikki tugmani sinang: ikkalasi ham raqamni 1 dan 5 gacha o'zgartiradi, har qadamda 200 ms ish qiladi va qadamlar orasida await qiladi. Farqi faqat nimani kutishida:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
.raqam { font: 700 3rem/1.2 ui-monospace, monospace; margin: 0; }
button { font: inherit; padding: 0.4rem 0.8rem; margin: 0.25rem 0; }
</style>
<p class="raqam" id="raqam">0</p>
<button id="microtask" type="button">await null</button>
<button id="task" type="button">await kut(0)</button>
<script>
const raqam = document.getElementById("raqam");
function bandQil(ms) {
const boshi = Date.now();
while (Date.now() - boshi < ms) {}
}
function kut(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function sana(nomi, bolakniKut) {
for (let i = 1; i <= 5; i++) {
raqam.textContent = i;
bandQil(200);
await bolakniKut();
}
console.log(`${nomi}: tugadi`);
}
const microtaskTugma = document.getElementById("microtask");
const taskTugma = document.getElementById("task");
microtaskTugma.addEventListener("click", () => {
sana("await null", () => null);
});
taskTugma.addEventListener("click", () => {
sana("await kut(0)", () => kut(0));
});
</script>Nimani ko'rdingiz:
- «await null» — bir soniya hech narsa o'zgarmaydi, keyin birdan 5. Har
awaitdan keyin davom microtask'da keldi — brauzerga chizishga navbat bermadi. - «await kut(0)» — raqamlar birin-ketin almashadi. Har davom — alohida task, ular orasida brauzer chizadi.
Biz buni Chrome 154 da tekshirdik: har kadrda (ekranning bitta chizilishida) raqamni yozib oldik. «await null» da kadrlar faqat 0 va 5 ni ko'rdi, «await kut(0)» da esa 1 dan 5 gacha hammasini.
Diqqat:
async/awaitkodni "asinxron qiladi" degan fikr — yarim haqiqat.awaitfaqat kutish paytida stekni bo'shatadi. Kutiladigan narsa tayyor bo'lsa (await null, tayyor Promise), davom shu task ichida, microtask sifatida keladi. Og'ir ishni bo'laklash uchun —await kut(0)yoki boshqa task.
Tekshirib ko'ring:
chekniSananing birinchi variantidaawait nullo'rnigaawait Promise.resolve()yozilsa, taymer sikl o'rtasida ishlaydimi?
Javob
Yo'q. Promise.resolve() — allaqachon bajarilgan Promise, uni kutish ham bitta microtask. Taymer baribir sikl oxirida chiqadi. Task navbatiga yo'l berish uchun kutiladigan Promise task orqali hal bo'lishi kerak — masalan, setTimeout ichida.
6. Brauzerda: tinglovchilar orasidagi microtask'lar
Microtask navbati qachon bo'shatiladi? Aniq qoida: stek bo'shagan har safar — faqat task oxirida emas. Brauzer bitta hodisa uchun bir nechta tinglovchini chaqirganda, har tinglovchidan keyin stek bo'shaydi. Demak har tinglovchidan keyin — microtask'lar.
Lekin hodisani kod chaqirsa (tugma.click()), stek bo'shamaydi: tinglovchilar click() ichidan, skript ustida ishlaydi. Bir xil tugma — ikki xil tartib:
<button id="tugma" type="button">Meni bosing</button>
<script>
const tugma = document.getElementById("tugma");
tugma.addEventListener("click", () => {
console.log("1-tinglovchi");
queueMicrotask(() => console.log("1-tinglovchining microtask'i"));
});
tugma.addEventListener("click", () => {
console.log("2-tinglovchi");
queueMicrotask(() => console.log("2-tinglovchining microtask'i"));
});
console.log("click() chaqirildi");
tugma.click();
console.log("click() qaytdi");
</script>Konsolda:
click() chaqirildi
1-tinglovchi
2-tinglovchi
click() qaytdi
1-tinglovchining microtask'i
2-tinglovchining microtask'iKod chaqirgan click() da ikkala tinglovchi skript ichida, ketma-ket ishladi. Microtask'lar — skript tugagach. Endi «Meni bosing» ni o'zingiz bosing:
1-tinglovchi
1-tinglovchining microtask'i
2-tinglovchi
2-tinglovchining microtask'iHaqiqiy bosishda brauzer tinglovchilarni o'zi chaqiradi: birinchisi tugagach stek bo'sh, microtask navbati bo'shatiladi, keyin ikkinchi tinglovchi. Ikkala tartibni ham Chrome 154 da sinab ko'rdik.
Bu farq testlarda ko'p uchraydi: test kodi click() bilan bosadi va tartib haqiqiy foydalanuvchinikidan farq qiladi. Tinglovchilar orasidagi tartibga tayanadigan kod yozmang.
7. Node'da nimasi boshqacha
Node'da ham shu qoidalar ishlaydi: then, await va queueMicrotask — bitta microtask navbati, har task (taymer, fayl callback'i) tugagach u to'liq bo'shatiladi. Bu darsdagi js misollarning hammasi Node 24 da tekshirilgan va brauzerda ham xuddi shu tartibni beradi.
Farq — Node'ning qo'shimcha process.nextTick navbatida (Event loop darsida aytilgan). U .cjs faylda Promise microtask'laridan oldin ishlaydi, .mjs faylning yuqori darajasida — keyin. Sababi va fazalar — Microtask navbati, process.nextTick va setImmediate darsida, 24-qismda. Yangi kodda queueMicrotask ni tanlang — u ikkala muhitda ham bir xil.
8. Ko'p uchraydigan xatolar
8.1 "await keyingi task'ga o'tkazadi" deb o'ylash
await davomni microtask qiladi. Taymerlar, bosishlar va chizish kutadi. Og'ir siklni await null yoki await Promise.resolve() bilan "bo'laklash" sahifani qutqarmaydi. Tuzatish — await kut(0) (task).
8.2 Microtask tafsilotlariga tayanish
"Bu then ikki qadam keyin keladi, demak u bilan sinxronlashadi" — mo'rt kod. Kichik o'zgarish (oraga bitta await qo'shish yoki return ni return await ga almashtirish) tartibni buzadi. Ikki ish orasidagi tartib muhim bo'lsa — ularni aniq bog'lang: bitta zanjir, ketma-ket await yoki Promise.all.
8.3 try ichida await siz return
return promise — catch ishlamaydi, xato chaqiruvchiga o'tib ketadi. Tuzatish — return await promise. Bu xatoni linter ham ushlaydi. Linter — kodni ishga tushirmasdan o'qib, shubhali joylarni ko'rsatadigan dastur. Eng mashhuri — ESLint, 12-qismda o'rnatamiz; hozir bilish shart emas.
8.4 Microtask'da xatoni yo'qotish
queueMicrotask(() => { throw new Error("...") }) — xato oddiy ushlanmagan xato bo'lib chiqadi va Node'da dasturni to'xtatadi. then ichidagi throw esa rad etilgan Promise'ga aylanadi. Ikkalasini ham ushlash kerak: queueMicrotask ichida — try/catch, Promise'da — catch. Ushlanmagan xatolarning umumiy himoyasini Ushlanmagan xatolar va global xato ushlash darsida ko'ramiz.
9. Mashqlar
1-mashq (oson): S, M yoki T?
Har console.log yoniga "S" (sinxron), "M" (microtask) yoki "T" (task) deb yozing va tartibni oldindan ayting:
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
async function f() {
console.log("D");
await null;
console.log("E");
}
f();
console.log("F");Tartib: A, [:D], F, C, [:E], B
Yechim
A — S, B — T, C — M, D — S, E — M, F — S.
Konsolda:
A
D
F
C
E
Bf() birinchi await gacha sinxron — "D" "F" dan oldin. "C" ning callback'i f() chaqirilishidan oldin navbatga qo'yilgan, shuning uchun "E" dan oldin. Taymer — oxirida.
2-mashq (o'rta): Zanjir va async aralash
Tartibni oldindan ayting, keyin ishga tushirib tekshiring. Ishora: har then bo'g'ini va har await dan keyingi qism — alohida microtask; async funksiya oddiy qiymat qaytarsa, uning then i bir qadamda keladi.
async function narx() {
console.log("1");
return 35000;
}
narx().then(() => console.log("2"));
Promise.resolve()
.then(() => console.log("3"))
.then(() => console.log("4"));
(async () => {
await null;
console.log("5");
await null;
console.log("6");
})();
console.log("7");Yechim
Konsolda:
1
7
2
3
5
4
6Sinxronlar: "1" (narx chaqirilganda), "7". Skript tugaganda microtask navbatida uchtasi bor, qo'yilgan tartibda: narx ning then i ("2"), birinchi zanjirning birinchi bo'g'ini ("3") va arrow funksiyaning birinchi await dan keyingi qismi ("5"). "3" bajarilgach "4" navbat oxiriga, "5" bajarilgach — "6". Shuning uchun "4" "6" dan oldin.
(async () => { ... })() — darhol chaqiriladigan async arrow funksiya (Promise yordamchilari darsida ko'rgansiz).
3-mashq (qiyin): Chizishni bir marta rejalashtirish
«Bahor» kassasida holat o'zgarganda chiz() funksiyasi chaqiriladi. Muammo: bitta amal (masalan, buyurtmani yopish) holatni uch marta o'zgartiradi — chiz() ham uch marta ishlaydi, foydalanuvchi esa faqat oxirgisini ko'radi (Event loop — chizish faqat task oxirida).
chizishniRejalashtir() funksiyasini yozing: u nechta marta chaqirilmasin, bitta sinxron kod bo'lagi davomida chiz() faqat bir marta — sinxron kod tugagach — ishlasin. Ishora: queueMicrotask va "allaqachon rejalashtirilganmi?" bayrog'i. Faylni kurs/mashqlar/11/11-microtask/rejalashtir.mjs ga saqlang.
Sinov: buyurtmaniYop() holatni uch marta o'zgartiradi va har safar chizishniRejalashtir() ni chaqiradi. Konsolda bitta "chizildi" qatori bo'lishi kerak — oxirgi holat bilan.
Yechim
const holat = { stol: 5, summa: 0, yopiq: false };
let rejalashtirilgan = false;
function chiz() {
console.log("chizildi:", JSON.stringify(holat));
}
function chizishniRejalashtir() {
if (rejalashtirilgan) {
return;
}
rejalashtirilgan = true;
queueMicrotask(() => {
rejalashtirilgan = false;
chiz();
});
}
function buyurtmaniYop() {
holat.summa = 63000;
chizishniRejalashtir();
holat.yopiq = true;
chizishniRejalashtir();
holat.stol = null;
chizishniRejalashtir();
console.log("buyurtma yopildi");
}
buyurtmaniYop();
setTimeout(() => {
holat.stol = 7;
chizishniRejalashtir();
}, 100);Konsolda:
buyurtma yopildi
chizildi: {"stol":null,"summa":63000,"yopiq":true}
chizildi: {"stol":7,"summa":63000,"yopiq":true}Birinchi chaqiruv microtask qo'yadi va bayroqni ko'taradi. Keyingi ikkitasi bayroqni ko'rib, qaytib ketadi. Sinxron kod tugagach microtask bir marta ishlaydi — oxirgi holat bilan. Bayroq microtask ichida tushiriladi: keyingi o'zgarish (100 ms dan keyingi taymer) yana yangi rejalashtirish qila oladi.
Nega microtask, taymer emas? Microtask — chizishdan oldin: brauzerda foydalanuvchi oraliq holatni hech qachon ko'rmaydi va kechikish ham yo'q. Vue va Lit kabi freymvorklar DOM yangilanishini aynan shunday — microtask'da, bir marta — bajaradi. Ular haqida keyingi qismlarda; hozir bilish shart emas.
git add 11/11-microtask/rejalashtir.mjs
git commit -m "11/11: chizishni microtask bilan bir marta rejalashtir"10. Real ishda
- Intervyu. «Intervyu masalasini yechamiz» dagi kod va uning variantlari — frontend intervyusining eng mashhur savollaridan. Tartibni qog'ozda, navbatlarni chizib yeching: sinxron → microtask'lar (zanjir bo'g'inlari birin-ketin) → task.
- Freymvorklar. React, Vue, Svelte holat o'zgarishlarini yig'ib (batching), bitta microtask yoki kadrda chizadi — 3-mashqdagi g'oya. React'da (17-qismda) holatni
setStatekabi funksiya o'zgartiradi. "NegasetStatedan keyin DOM darhol o'zgarmadi?" degan savolning javobi shu. - Sezgir interfeys. Uzun hisob-kitobni
asyncfunksiyaga o'rab,awaitli sikl yozish — ko'p uchraydigan xato. Brauzer qotadi, chunkiawaittask'ga yo'l bermaydi. To'g'risi — task orqali bo'laklash yoki Asosiy oqimni bo'shatish darsidagischeduler.yield(). - Node serverlari. Microtask ochligi serverda ham bor:
awaitli cheksiz sikl boshqa so'rovlarning callback'larini (task) kutdirib qo'yadi — server "osilib" qoladi.
Xulosa
- Promise callback'i microtask navbatiga Promise hal bo'lganda tushadi; zanjirning keyingi bo'g'ini — oldingisi bajarilgach. Shuning uchun ikki zanjir almashib ishlaydi.
thenyokiasyncfunksiyadan Promise qaytarish — ochish uchun ikki qo'shimcha microtask.asyncfunksiya birinchiawaitgacha sinxron;awaitdan keyingi qism — microtask (await nullda ham).tryichida —return await, aks holdacatchrad etilishni ushlamaydi.- Microtask ochligi: microtask'lar zanjiri tugamaguncha taymerlar, hodisalar va chizish kutadi;
await nullsahifani "bo'shatmaydi",await kut(0)bo'shatadi. - Brauzer microtask'larni stek bo'shagan har safar bajaradi — haqiqiy bosishda har tinglovchidan keyin.
Keyingi dars: Bekor qilish: AbortController — keraksiz bo'lib qolgan asinxron ishni to'xtatish: AbortSignal, vaqt chegarasi uchun AbortSignal.timeout va bir nechta signalni birlashtiradigan AbortSignal.any.
Manbalar
- HTML Living Standard: "Event loops", "Perform a microtask checkpoint", "Clean up after running script" — html.spec.whatwg.org
- ECMAScript spetsifikatsiyasi: "NewPromiseReactionJob", "NewPromiseResolveThenableJob", "Await" — tc39.es/ecma262
- Jake Archibald, "Tasks, microtasks, queues and schedules" (tinglovchilar va
click()misoli) — jakearchibald.com - V8 blog: "Faster async functions and promises" (
awaitqadamlari) — v8.dev/blog - MDN: "Using microtasks in JavaScript with queueMicrotask()" — developer.mozilla.org
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!