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:
- Serverdan ma'lumot yuklab olish (tarmoq so'rovi);
- Ma'lum vaqt kutish (
setTimeout); - Diskdan fayl o'qish (Node.js);
- Tashqi skript yoki modulni yuklash.
Bunday harakatlar sekin bo'lishi mumkin, shuning uchun JavaScript ularni kutib turmaydi โ kod bajarilishda davom etadi, natija esa tayyor bo'lganda alohida yetkaziladi.
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.
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:
- Callback'ning birinchi argumenti โ xatolik uchun ajratiladi. Agar xatolik bo'lsa, u shu yerga uzatiladi;
- Keyingi argumentlar โ muvaffaqiyatli natijalar uchun;
- Agar xatolik bo'lmasa, birinchi argument
nullbo'ladi.
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.
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.
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:
Bu misolda callback darhol (sinxron) chaqiriladi, shuning uchun natija konsolda ko'rinadi. Real asinxron misollarda esa (setTimeout, tarmoq so'rovi) callback keyinroq chaqiriladi.
Xulosa
- Asinxron harakat โ hozir boshlanib, keyin tugaydigan ish (setTimeout, tarmoq, fayl);
- Callback โ asinxron harakat tugaganda chaqirilishi uchun boshqa funksiyaga uzatilgan funksiya;
- Error-first uslub โ callback'ning birinchi argumenti xatolik uchun, keyingilari natija uchun;
- Ketma-ket ko'p asinxron ish callback'lar ichma-ich joylashuviga โ callback jahannamiga olib keladi;
- Bu muammoni Promise chiroyli hal qiladi โ keyingi darsda o'rganamiz.