16-bo‘lim

Asinxron JavaScript

Bir oqimli model, hodisalar sikli, setTimeout, callback lar va callback jahannami, mikro va makro navbatlar.

🕑 16 daqiqa o‘qish 📄 755 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Sinxron kod bloklaydi
  2. setTimeout - kodni keyinga qoldirish
  3. Hodisalar sikli (event loop)
  4. Mikro va makro navbat
  5. Callback naqshi
  6. Callback jahannami
  7. try/catch asinxron kodda ishlamaydi
  8. setInterval va uni to'xtatish
  9. Amaliy misol: kechiktirish funksiyasi
  10. Xulosa

JavaScript bitta oqimda ishlaydi. Shunga qaramay u fayl yuklab, taymer ishlatib, server bilan gaplashadi - hammasini bir vaqtda. Bu qanday ishlaydi?

Sinxron kod bloklaydi #

JavaScript
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");
Natija
Boshlandi
Natija olindi: true
Tugadi
Brauzerda og'ir hisob sahifani muzlatadi

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:

  1. Uni kichik bo'laklarga bo'ling va orasida brauzerga nafas bering;
  2. Yoki Web Worker ga bering - u alohida oqimda ishlaydi:
JavaScript
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 #

JavaScript
console.log("1 - birinchi");

setTimeout(() => {
    console.log("3 - taymerdan");
}, 0);

console.log("2 - ikkinchi");
Natija
1 - birinchi
2 - ikkinchi
3 - taymerdan
setTimeout(fn, 0) "darhol" degani emas

0 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:

JavaScript
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) #

Hodisalar sikli Chaqiruvlar steki ichki() tashqi() main() Hodisalar sikli bo'shmi? Mikro navbat Promise, queueMicrotask Makro navbat setTimeout, hodisalar, I/O Har aylanishda tartib 1. Stek bo'shashini kutadi 2. MIKRO navbatni TO'LIQ bo'shatadi 3. Makro navbatdan FAQAT BITTA vazifa oladi
Mikro navbat makro navbatdan har doim ustun turadi

Mikro va makro navbat #

JavaScript
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");
Natija
1 - sinxron
2 - sinxron
3 - mikro (Promise)
4 - mikro (queueMicrotask)
5 - makro (setTimeout)
Tartibni eslab qolish qoidasi
  1. Barcha sinxron kod - to'liq oxirigacha;
  2. Barcha mikro vazifalar - navbat butunlay bo'shaguncha;
  3. Bitta makro vazifa;
  4. 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:

JavaScript
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 #

JavaScript
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");
Natija
So'rovlar yuborildi
Olindi: Foydalanuvchi 1
Xato: Noto'g'ri id
"Xato birinchi" (error-first) kelishuvi

Node.js da callback ning birinchi argumenti - xato:

JavaScript
callback(xato, natija);

Xato bo'lmasa null uzatiladi. Bu kelishuv butun ekotizimda qabul qilingan, shuning uchun har bir callback shunday boshlanadi:

JavaScript
(xato, natija) => {
    if (xato) {
        // xatoni qayta ishlash
        return;
    }
    // natija bilan ishlash
}

return ni unutmang - aks holda kod xatodan keyin ham davom etadi.

Callback jahannami #

JavaScript
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");
            });
        });
    });
});
Natija
Ichma-ich callback lar:
  1-bosqich tugadi
  2-bosqich tugadi
  3-bosqich tugadi
  4-bosqich tugadi
Hammasi tugadi
Callback jahannami - nima uchun muammo

Yuqoridagi kod ishlaydi, lekin o'ngga qarab o'sib boradi. Real loyihada u shunday ko'rinadi:

JavaScript
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:

MuammoTa'siri
Chuqur ichma-ichlikO'qib bo'lmaydi
Har darajada xato tekshiruviTakrorlanish
Parallel bajarish qiyinSekin ishlaydi
try/catch ishlamaydiXato ushlanmaydi

Oxirgisi eng jiddiy - keyingi bo'limda ko'ramiz.

Yechim - Promise va async/await. Yuqoridagi kod ular bilan shunday bo'ladi:

JavaScript
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 #

JavaScript
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");
}
Natija
try bloki tugadi
Callback ichida xato: kechikkan xato
Nima uchun try/catch ushlamaydi

try 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:

Natija
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 #

JavaScript
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);
Natija
  tik 1
  tik 2
  tik 3
To'xtatildi
Yakuniy sanoq: 3
clearInterval ni unutmang

setInterval 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:

JavaScript
tugma.addEventListener("click", () => {
    setInterval(yangilash, 1000);      // har bosishda YANGI taymer!
});

Uch marta bosilsa - uchta taymer parallel ishlaydi.

To'g'ri:

JavaScript
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:

JavaScript
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 #

JavaScript
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");
Natija
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.

Amaliy topshiriq
  1. Og'ir sikl brauzerni bloklashini tushuntiring.
  2. setTimeout(fn, 0) nima uchun darhol ishlamasligini ayting.
  3. Sinxron, mikro va makro kod tartibini isbotlang.
  4. queueMicrotask va setTimeout ni taqqoslang.
  5. "Xato birinchi" callback yozing.
  6. To'rt darajali ichma-ich callback yozib, muammoni ko'ring.
  7. try/catch asinxron xatoni ushlamasligini isbotlang.
  8. setInterval ni uch marta ishlagach to'xtating.
  9. Har bosishda yangi taymer yaratish xatosini tuzating.
  10. kutish funksiyasini 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/catch asinxron callback xatosini ushlamaydi.
  • setInterval ni clearInterval bilan to'xtating.
  • Ish tugashini kutish kerak bo'lsa - rekursiv setTimeout.

Keyingi bo'limda Promise va async/await bilan bu muammolarni hal qilamiz.

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.