16-bo‘lim
Asinxron JavaScript
Bir oqimli model, hodisalar sikli, setTimeout, callback lar va callback jahannami, mikro va makro navbatlar.
Ushbu bo‘lim mundarijasi
JavaScript bitta oqimda ishlaydi. Shunga qaramay u fayl yuklab, taymer ishlatib, server bilan gaplashadi - hammasini bir vaqtda. Bu qanday ishlaydi?
Sinxron kod bloklaydi #
function ogirHisob() {
let yigindi = 0;
for (let i = 0; i < 50000000; i++) {
yigindi += i;
}
return yigindi;
}
console.log("Boshlandi");
const natija = ogirHisob();
console.log("Natija olindi:", natija > 0);
console.log("Tugadi");
Boshlandi
Natija olindi: true
Tugadi
JavaScript brauzerda bitta oqimda ishlaydi. Bu oqim quyidagilarning hammasini bajaradi:
- Sizning kodingiz;
- Hodisa ishlovchilari;
- Sahifani chizish (rendering);
- Foydalanuvchi kiritishiga javob.
Yuqoridagi ogirHisob ishlayotganda foydalanuvchi:
- Tugmani bosa olmaydi;
- Sahifani aylantira olmaydi;
- Matn yoza olmaydi.
Brauzer hatto "Sahifa javob bermayapti" oynasini ko'rsatishi mumkin.
Uzoq hisob kerak bo'lsa:
- Uni kichik bo'laklarga bo'ling va orasida brauzerga nafas bering;
- Yoki Web Worker ga bering - u alohida oqimda ishlaydi:
const ishchi = new Worker("hisob.js");
ishchi.postMessage({ son: 50000000 });
ishchi.onmessage = (h) => console.log("Natija:", h.data);
Node.js da bu worker_threads moduli.
setTimeout - kodni keyinga qoldirish #
console.log("1 - birinchi");
setTimeout(() => {
console.log("3 - taymerdan");
}, 0);
console.log("2 - ikkinchi");
1 - birinchi
2 - ikkinchi
3 - taymerdan
setTimeout(fn, 0) "darhol" degani emas0 millisekund berilgan bo'lsa ham, funksiya darhol
ishlamaydi.
U navbatga qo'yiladi va faqat joriy kod to'liq tugagandan keyin bajariladi.
setTimeout ning ikkinchi argumenti - "shu vaqtdan keyin",
"aynan shu vaqtda" emas. Agar asosiy oqim band bo'lsa, taymer
kutadi:
setTimeout(() => console.log("men"), 10);
ogirHisob(); // 3 soniya ishlaydi
// "men" 10 ms emas, ~3000 ms dan keyin chiqadi
Brauzerlarda yana bir cheklov bor: ichma-ich setTimeout
chaqiruvlari 5 darajadan keyin kamida 4 ms ga
majburlanadi.
Hodisalar sikli (event loop) #
Mikro va makro navbat #
console.log("1 - sinxron");
setTimeout(() => console.log("5 - makro (setTimeout)"), 0);
Promise.resolve().then(() => console.log("3 - mikro (Promise)"));
queueMicrotask(() => console.log("4 - mikro (queueMicrotask)"));
console.log("2 - sinxron");
1 - sinxron
2 - sinxron
3 - mikro (Promise)
4 - mikro (queueMicrotask)
5 - makro (setTimeout)
- Barcha sinxron kod - to'liq oxirigacha;
- Barcha mikro vazifalar - navbat butunlay bo'shaguncha;
- Bitta makro vazifa;
- Yana mikro navbat, yana bitta makro... va hokazo.
Mikro navbat to'liq bo'shatiladi - agar mikro vazifa ichida yana mikro vazifa qo'shilsa, u ham shu aylanishda bajariladi.
Bu xavfli holatga olib kelishi mumkin:
function cheksizMikro() {
Promise.resolve().then(cheksizMikro);
}
cheksizMikro(); // sahifa muzlaydi - makro navbatga navbat kelmaydi
setTimeout bilan esa bunday bo'lmaydi - har aylanishda faqat
bitta makro vazifa olinadi, orada brauzer sahifani
chizishga ulguradi.
Callback naqshi #
function malumotOlish(id, callback) {
// Server so'rovini taqlid qilamiz
setTimeout(() => {
if (id <= 0) {
callback(new Error("Noto'g'ri id"), null);
return;
}
callback(null, { id: id, ism: "Foydalanuvchi " + id });
}, 10);
}
malumotOlish(1, (xato, malumot) => {
if (xato) {
console.log("Xato:", xato.message);
return;
}
console.log("Olindi:", malumot.ism);
});
malumotOlish(-1, (xato, malumot) => {
if (xato) {
console.log("Xato:", xato.message);
return;
}
console.log("Olindi:", malumot.ism);
});
console.log("So'rovlar yuborildi");
So'rovlar yuborildi
Olindi: Foydalanuvchi 1
Xato: Noto'g'ri id
Node.js da callback ning birinchi argumenti - xato:
callback(xato, natija);
Xato bo'lmasa null uzatiladi. Bu kelishuv butun ekotizimda
qabul qilingan, shuning uchun har bir callback shunday
boshlanadi:
(xato, natija) => {
if (xato) {
// xatoni qayta ishlash
return;
}
// natija bilan ishlash
}
return ni unutmang - aks holda kod xatodan keyin ham davom
etadi.
Callback jahannami #
function bosqich(nom, keyingi) {
setTimeout(() => {
console.log(" " + nom + " tugadi");
keyingi();
}, 5);
}
console.log("Ichma-ich callback lar:");
bosqich("1-bosqich", () => {
bosqich("2-bosqich", () => {
bosqich("3-bosqich", () => {
bosqich("4-bosqich", () => {
console.log("Hammasi tugadi");
});
});
});
});
Ichma-ich callback lar:
1-bosqich tugadi
2-bosqich tugadi
3-bosqich tugadi
4-bosqich tugadi
Hammasi tugadi
Yuqoridagi kod ishlaydi, lekin o'ngga qarab o'sib boradi. Real loyihada u shunday ko'rinadi:
foydalanuvchiOlish(id, (x1, foydalanuvchi) => {
if (x1) { return xatoQayta(x1); }
buyurtmalarOlish(foydalanuvchi.id, (x2, buyurtmalar) => {
if (x2) { return xatoQayta(x2); }
mahsulotOlish(buyurtmalar[0].id, (x3, mahsulot) => {
if (x3) { return xatoQayta(x3); }
narxHisoblash(mahsulot, (x4, narx) => {
if (x4) { return xatoQayta(x4); }
korsatish(narx);
});
});
});
});
Muammolari:
| Muammo | Ta'siri |
|---|---|
| Chuqur ichma-ichlik | O'qib bo'lmaydi |
| Har darajada xato tekshiruvi | Takrorlanish |
| Parallel bajarish qiyin | Sekin ishlaydi |
try/catch ishlamaydi | Xato ushlanmaydi |
Oxirgisi eng jiddiy - keyingi bo'limda ko'ramiz.
Yechim - Promise va async/await. Yuqoridagi kod
ular bilan shunday bo'ladi:
try {
const foydalanuvchi = await foydalanuvchiOlish(id);
const buyurtmalar = await buyurtmalarOlish(foydalanuvchi.id);
const mahsulot = await mahsulotOlish(buyurtmalar[0].id);
korsatish(await narxHisoblash(mahsulot));
} catch (xato) {
xatoQayta(xato);
}
try/catch asinxron kodda ishlamaydi #
function xatoTashlaydi(callback) {
setTimeout(() => {
callback(new Error("kechikkan xato"));
}, 5);
}
// try/catch ni sinab ko'ramiz
try {
xatoTashlaydi((xato) => {
console.log("Callback ichida xato:", xato.message);
});
console.log("try bloki tugadi");
} catch (xato) {
console.log("BU HECH QACHON CHIQMAYDI");
}
try bloki tugadi
Callback ichida xato: kechikkan xato
try/catch ushlamayditry bloki xatoTashlaydi ni chaqiradi va darhol tugaydi.
Taymer esa 5 ms keyin ishlaydi - o'shanda try bloki
allaqachon yopilgan.
Chaqiruvlar steki butunlay boshqa bo'ladi:
try boshlangan payt: main -> xatoTashlaydi
callback ishlagan payt: (yangi stek) -> callback
Ular orasida hech qanday bog'liqlik yo'q.
Shuning uchun callback naqshida xato argument orqali uzatiladi.
async/await bu muammoni hal qiladi - u try/catch ni
asinxron kodda ishlaydigan qiladi (17-bo'lim).
setInterval va uni to'xtatish #
let sanoq = 0;
const id = setInterval(() => {
sanoq++;
console.log(" tik", sanoq);
if (sanoq >= 3) {
clearInterval(id);
console.log("To'xtatildi");
}
}, 5);
setTimeout(() => {
console.log("Yakuniy sanoq:", sanoq);
}, 100);
tik 1
tik 2
tik 3
To'xtatildi
Yakuniy sanoq: 3
clearInterval ni unutmangsetInterval abadiy ishlaydi. Uni to'xtatmasangiz:
- Xotira sizadi;
- Sahifadan chiqilganda ham ishlashda davom etadi;
- Bir necha marta ishga tushirilsa - bir necha taymer ishlaydi.
Ayniqsa xavfli naqsh:
tugma.addEventListener("click", () => {
setInterval(yangilash, 1000); // har bosishda YANGI taymer!
});
Uch marta bosilsa - uchta taymer parallel ishlaydi.
To'g'ri:
let taymer = null;
tugma.addEventListener("click", () => {
clearInterval(taymer); // eskisini to'xtatamiz
taymer = setInterval(yangilash, 1000);
});
Ko'p hollarda setInterval o'rniga rekursiv setTimeout
afzalroq - u oldingi bajarilish tugashini kutadi:
function takrorlash() {
ogirIsh();
setTimeout(takrorlash, 1000); // ish tugagach 1 soniya kutadi
}
setInterval esa ish tugashini kutmaydi va vazifalar
bir-birining ustiga chiqishi mumkin.
Amaliy misol: kechiktirish funksiyasi #
function kutish(millisekund) {
return new Promise((bajarildi) => setTimeout(bajarildi, millisekund));
}
async function bosqichlar() {
console.log("Boshlandi");
await kutish(10);
console.log(" 10 ms o'tdi");
await kutish(10);
console.log(" yana 10 ms o'tdi");
console.log("Tugadi");
}
bosqichlar();
console.log("Bu darhol chiqadi");
Boshlandi
Bu darhol chiqadi
10 ms o'tdi
yana 10 ms o'tdi
Tugadi
Bu yerda Promise va async/await ishlatildi - ular keyingi
bo'limning mavzusi.
- Og'ir sikl brauzerni bloklashini tushuntiring.
setTimeout(fn, 0)nima uchun darhol ishlamasligini ayting.- Sinxron, mikro va makro kod tartibini isbotlang.
queueMicrotaskvasetTimeoutni taqqoslang.- "Xato birinchi" callback yozing.
- To'rt darajali ichma-ich callback yozib, muammoni ko'ring.
try/catchasinxron xatoni ushlamasligini isbotlang.setIntervalni uch marta ishlagach to'xtating.- Har bosishda yangi taymer yaratish xatosini tuzating.
kutishfunksiyasini yozib, ketma-ket ishlating.
Xulosa #
- JavaScript bitta oqimda ishlaydi - og'ir hisob hammani bloklaydi.
- Uzoq hisob uchun Web Worker yoki bo'laklash.
setTimeout(fn, 0)darhol emas - navbatga qo'yiladi.- Hodisalar sikli: sinxron → barcha mikro → bitta makro.
- Mikro navbat (Promise) makro navbatdan ustun.
- Cheksiz mikro vazifa sahifani muzlatadi.
- Callback da "xato birinchi" kelishuvi qo'llanadi.
- Callback jahannami o'qishni va parallel bajarishni qiyinlashtiradi.
try/catchasinxron callback xatosini ushlamaydi.setIntervalniclearIntervalbilan to'xtating.- Ish tugashini kutish kerak bo'lsa - rekursiv
setTimeout.
Keyingi bo'limda Promise va async/await bilan bu muammolarni
hal qilamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.