Promise, async/await

Kirish: callback'lar

JavaScript'da ko'p funksiyalar asinxron harakatlarni โ€” ya'ni hozir boshlanib, keyinroq tugaydigan ishlarni โ€” rejalashtiradi. Bunday harakatlar tugaganda natijani qanday qabul qilamiz? Eng qadimiy va sodda usul โ€” callback funksiyalar. Ushbu darsda callback g'oyasini, uning kuchli va zaif tomonlarini ko'rib chiqamiz.

Asinxron harakat nima?

Asinxron harakat โ€” bu hozir boshlanadigan, ammo kelajakda (keyinroq) yakunlanadigan ish. Masalan:

Bunday harakatlar sekin bo'lishi mumkin, shuning uchun JavaScript ularni kutib turmaydi โ€” kod bajarilishda davom etadi, natija esa tayyor bo'lganda alohida yetkaziladi.

JavaScript bir oqimli (single-threaded) tildir. Ya'ni u bir vaqtda faqat bitta ishni bajaradi. Asinxron mexanizmlar aynan shu tufayli muhim: sekin ishlarni kutib turmasdan, dastur bloklanmasligini ta'minlaydi.

setTimeout โ€” eng sodda asinxron misol

Keling, biror kodni ma'lum vaqtdan keyin ishga tushiruvchi setTimeout misolidan boshlaymiz. Uning ikkinchi argumenti โ€” millisekundlardagi kutish vaqti:

console.log("Boshlandi");

setTimeout(function () {
  console.log("1 sekunddan keyin");
}, 1000);

console.log("Tugadi");

// Konsolda tartib:
// Boshlandi
// Tugadi
// 1 sekunddan keyin   <- eng oxirida, 1 sekunddan so'ng

E'tibor bering: "Tugadi" so'zi "1 sekunddan keyin"dan oldin chiqdi. Chunki setTimeout ichidagi funksiya darhol bajarilmaydi โ€” u keyinroq chaqiriladi. Kod esa uni kutib turmaydi, pastga davom etadi.

Mana bu funksiya โ€” setTimeoutga uzatilgan va "keyin chaqirib qo'ying" deb topshirilgan funksiya โ€” callback deb ataladi.

Callback g'oyasi

Callback (so'zma-so'z "qayta chaqirish") โ€” bu boshqa funksiyaga argument sifatida uzatiladigan va asinxron harakat tugaganda chaqiriladigan funksiya. G'oya sodda: "Sen ishingni bajar, tugatgach mana bu funksiyani chaqir".

Faraz qilaylik, biror skriptni yuklab, so'ng u yuklangach biror ish qilmoqchimiz. Yuklash asinxron bo'lgani uchun, "yuklangandan keyin"gi kodni callback shaklida uzatamiz:

function loadScript(src, callback) {
  const script = document.createElement("script");
  script.src = src;

  // Skript yuklanib bo'lganda brauzer callback'ni chaqiradi
  script.onload = function () {
    callback(script);
  };

  document.head.append(script);
}

// Foydalanish:
loadScript("/my/script.js", function (script) {
  console.log("Skript yuklandi: " + script.src);
  // Endi skript ichidagi funksiyalardan foydalanishimiz mumkin
});

Bu yerda loadScript ikkinchi argument sifatida funksiya (callback) qabul qiladi va skript yuklangach uni chaqiradi. Shunday qilib, biz asinxron harakat tugaganidan keyin bajariladigan kodni belgilay oldik.

Callback'ning asosiy afzalligi: u sodda va tushunarli. Kichik, bir bosqichli asinxron ishlar uchun bu yechim juda qulay.

Xatolik-birinchi (error-first) callback

Yuqoridagi misolda hammasi yaxshi ketishini nazarda tutdik. Ammo real hayotda xatolik bo'lishi mumkin โ€” skript topilmasligi, tarmoq uzilishi va hokazo. Callback bunday holatni ham qabul qila olishi kerak.

JavaScript va ayniqsa Node.js hamjamiyatida bu muammo uchun error-first callback (xatolik-birinchi callback) uslubi qabul qilingan. Uning qoidalari:

function loadScript(src, callback) {
  const script = document.createElement("script");
  script.src = src;

  script.onload = function () {
    // Xatolik yo'q: birinchi argument null, ikkinchisi natija
    callback(null, script);
  };

  script.onerror = function () {
    // Xatolik bor: uni birinchi argumentga uzatamiz
    callback(new Error("Skript yuklanmadi: " + src));
  };

  document.head.append(script);
}

// Foydalanish:
loadScript("/my/script.js", function (error, script) {
  if (error) {
    console.log("Xatolik yuz berdi: " + error.message);
  } else {
    console.log("Muvaffaqiyat: " + script.src);
  }
});

Endi bitta callback ikki xil vaziyatni ham boshqaradi: avval errorni tekshiramiz, xatolik bo'lmasa natija bilan ishlaymiz. Bu uslub juda keng tarqalgan va uni bilish shart.

"Error-first" nomi shundan: xatolik doim birinchi argumentda turadi. Node.js'ning ko'pgina o'rnatilgan funksiyalari (masalan, fs.readFile) aynan shu qoidaga amal qiladi.

Callback jahannami (pyramid of doom)

Callback bitta asinxron harakat uchun ajoyib. Lekin ketma-ket bir nechta asinxron ishni bajarishimiz kerak bo'lsa-chi? Masalan: bitta skriptni yuklab, u yuklangach ikkinchisini, keyin uchinchisini yuklashimiz kerak. Har biri oldingisining callback'i ichida yozilishi kerak:

loadScript("1.js", function (error, script) {
  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript("2.js", function (error, script) {
      if (error) {
        handleError(error);
      } else {
        // ...
        loadScript("3.js", function (error, script) {
          if (error) {
            handleError(error);
          } else {
            // ...uchala skript ham yuklandi
          }
        });
      }
    });
  }
});

Har bir yangi asinxron harakat qo'shilgani sayin kod o'ngga qarab, ichkariga chuqurroq "tishlab" boradi. Chaqiruvlar bir-birining ichiga joylashib, uchburchak (piramida) hosil qiladi. Har birida yana if (error) shoxlanishi bor.

Bu holat "callback jahannami" (callback hell) yoki "pyramid of doom" (halokat piramidasi) deb ataladi. Kod o'qish qiyinlashadi, xatolikni boshqarish takrorlanaveradi, o'zgartirish esa qo'rqinchli bo'lib qoladi.

Ichma-ich joylashuv chuqurlashgani sayin kod "tortmaga sig'maydigan" holatga keladi. Buni funksiyalarni alohida ajratib biroz yumshatish mumkin, lekin muammo tubdan hal bo'lmaydi โ€” kod baribir tarqoq bo'lib qoladi.

Aynan shu muammoni chiroyli hal qilish uchun JavaScript'ga Promise (va'da) tushunchasi kiritilgan. Keyingi darslarda uni o'rganamiz โ€” u asinxron kodni tekis va tushunarli yozishga imkon beradi.

Sodda amaliy misol

Callback shunchaki "boshqa funksiyaga uzatilgan funksiya" ekanini yaxshi anglash uchun, sinxron (darhol ishlaydigan) sodda misolni ko'ramiz:

callback.js
// natija shu yerda chiqadi

Bu misolda callback darhol (sinxron) chaqiriladi, shuning uchun natija konsolda ko'rinadi. Real asinxron misollarda esa (setTimeout, tarmoq so'rovi) callback keyinroq chaqiriladi.

Xulosa