Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. Asinxron callback
- 2.1 O'z asinxron funksiyamiz
- 2.2 Callback — shartnoma
- 2.3 Callback — oxirgi parametr
- 3. Asinxron xatolar
- 3.1 try/catch nega ushlamaydi
- 3.2 Error-first callback
- 3.3 Node'da: haqiqiy error-first
- 4. Callback hell
- 4.1 Bir-biriga bog'liq ishlar
- 4.2 Piramida nima uchun yomon
- 4.3 Parallel ishlar: hisoblagich bilan
- 4.4 Tekislash: nomli funksiyalar
- 5. Inversion of control
- 5.1 Boshqaruvni topshirish
- 5.2 Ikki marta chaqirish
- 5.3 Shartnomaning boshqa buzilishlari
- 5.4 Goh sinxron, goh asinxron
- 6. Ko'p uchraydigan xatolar
- 6.1 Xatodan keyin return ni unutish
- 6.2 Xatoni tekshirmaslik
- 6.3 Callback ichidagi throw
- 6.4 Natijani tashqariga "olib chiqish"
- 7. Mashqlar
- 1-mashq (oson): Error-first bron
- 2-mashq (o'rta): Piramidani tekislang
- 3-mashq (qiyin): Ketma-ket bajaruvchi va vaqt chegarasi
- 8. Real ishda
- Xulosa
- Manbalar
JavaScript callback'lari va callback hell: asinxron natijani olish
Qisqacha: Asinxron natija hali yo'q paytda funksiya qaytib bo'ladi, shuning uchun natija callback orqali beriladi:
narxniSora("osh", (xato, narx) => ...). Node'da kelishuv — error-first: callback'ning birinchi argumenti xato yokinull. Bir-biriga bog'liq bir nechta asinxron ish ichma-ich callback'larga — callback hell piramidasiga aylanadi. Callback'ni begona kodga berganda esa uning qachon va necha marta chaqirilishini nazorat qila olmaysiz — bu inversion of control.
Bu darsda
- O'z asinxron funksiyangizni yozasiz: natijani callback orqali qaytaradigan.
- Asinxron xatoni nega
try/catchushlamasligini ko'rasiz va error-first callback kelishuvini qo'llaysiz. - Callback hell qanday paydo bo'lishini va uni nomli funksiyalar bilan qanday tekislashni bilasiz.
- Inversion of control xavflarini (ikki marta chaqirish, umuman chaqirmaslik, goh sinxron, goh asinxron) taniysiz va ulardan himoyalanasiz.
Oldin bilishingiz kerak: Sinxron va asinxron kod, Taymerlar, try / catch / finally, Closure amaliyotda.
1. Nega bu kerak?
Sinxron va asinxron kod darsida Sardorning xatosini ko'rgan edik: narxniSora ichidagi setTimeout callback'i return qilgan narx hech qayerga yetib bormadi — funksiya undefined qaytardi. Asinxron natija hali yo'q paytda funksiya allaqachon tugaydi.
Yechim ham o'sha darsda bor edi: "javobni kimga berish kerak" degan funksiyani oldindan berib qo'yish. Ofitsiant Otabek oshxonaga buyurtma berib, "tayyor bo'lsa, menga qo'ng'iroq qiling" deydi. Qo'ng'iroq — callback.
Bu g'oya JavaScript'da 20 yil davomida asinxronlikning asosiy vositasi bo'lgan. Node'ning birinchi versiyalaridagi hamma fayl va tarmoq funksiyalari, brauzerdagi hodisalar — hammasi callback bilan ishlaydi. Bugun ham eski kodda, kutubxonalarda va Node'ning o'zida ularni har kuni uchratasiz.
Bugun callback'ni oxirigacha o'rganamiz — kuchli tomonlarini ham, nega keyinchalik undan yaxshiroq vosita kerak bo'lganini ham.
2. Asinxron callback
2.1 O'z asinxron funksiyamiz
Asinxron callback — asinxron ish tugaganda, natija bilan chaqiriladigan funksiya. Uni qabul qiladigan funksiya darhol qaytadi, callback esa keyinroq ishlaydi.
Oshxonadan narx so'raymiz. Haqiqiy oshxona javob berishga vaqt sarflaydi — buni setTimeout bilan taqlid qilamiz:
const menyu = { osh: 35000, manti: 30000 };
function narxniSora(taom, callback) {
setTimeout(() => {
callback(menyu[taom]);
}, 300);
}
console.log("Oshxonadan so'raldi");
narxniSora("manti", (narx) => {
console.log(`Manti: ${narx} so'm`);
});
console.log("Kutish davomida choy quyildi");Konsolda:
Oshxonadan so'raldi
Kutish davomida choy quyildi
Manti: 30000 so'mnarxniSora natijani return qilmaydi. U callback parametrini oladi va natija tayyor bo'lganda uni chaqiradi, natijani argument qilib. Biz esa natijani ishlatadigan kodni — console.log ni — callback ichiga yozdik.
Qadamma-qadam qarang. O'ngda o'zgaruvchilar o'rnida — host'dagi taymer (Web API) va Navbat:
E'tibor bering: callback chaqirilganda narxniSora ning konteksti stekda yo'q — u ancha oldin tugagan. Lekin taymer callback'i taom va callback ni closure orqali eslab qolgan (Closure). Asinxron callback'lar closure'siz ishlamasdi.
2.2 Callback — shartnoma
Callback qabul qiladigan har funksiya bilan sizning orangizda shartnoma bor: callback qachon, necha marta va qanday argumentlar bilan chaqiriladi. Massiv metodlarida bu oddiy: darhol, har element uchun bir marta. Asinxron funksiyada esa shartnoma odatda shunday:
- callback keyinroq chaqiriladi — funksiya qaytgandan keyin;
- aynan bir marta — muvaffaqiyatda ham, xatoda ham;
- argumentlar tartibi hujjatda yozilgan.
Bu shartnomaning har bandi buzilishi mumkin. Dars oxirida, «Inversion of control» bo'limida, buzilganda nima bo'lishini ko'ramiz.
Tekshirib ko'ring:
narxniSora("osh", console.log)deb chaqirsak nima chiqadi va qachon?
Javob
300 ms dan keyin 35000 chiqadi. console.log ham funksiya — uni callback qilib berish mumkin. narxniSora uni console.log(35000) ko'rinishida chaqiradi.
2.3 Callback — oxirgi parametr
narxniSora(taom, callback) da callback oxirida turibdi. Bu tasodif emas — Node va ko'p kutubxonalardagi kelishuv. Ikki sababi bor.
Birinchisi — o'qish qulayligi. Callback ko'pincha bir necha qatorli arrow funksiya bo'ladi. U oxirida tursa, qisqa argumentlar boshida ko'rinadi, uzun tana esa pastga cho'ziladi:
narxniSora("manti", (narx) => {
// bir necha qator...
});Callback birinchi bo'lganda "manti" uzun tananing ostida, yopuvchi qavs yonida "yo'qolib" qolardi.
Ikkinchisi — ixtiyoriy parametrlar. Node'ning readFile(yol, kodlash, callback) funksiyasini o'tgan darsda "utf8" bilan chaqirgan edik — o'sha "utf8" kodlash parametri. Uni tashlab ketish mumkin: readFile(yol, callback). Funksiya oxirgi argument funksiya ekanini ko'rib, uni callback deb oladi. Callback o'rtada bo'lsa, buni qilib bo'lmasdi.
setTimeout(fn, ms) bu qoidaga bo'ysunmaydi — u Node'dan ancha oldin, brauzerda paydo bo'lgan. Shuning uchun taymerda callback birinchi.
Tekshirib ko'ring: Siz
stolniTop(odam, zal, callback)funksiyasini yozdingiz. Hamkasbingiz unistolniTop(4, (xato, stol) => {...}, "ayvon")deb chaqirdi. Nima bo'ladi?
Javob
zal parametriga funksiya, callback ga esa "ayvon" satri tushadi. Funksiya natija tayyor bo'lganda callback(...) ni chaqirmoqchi bo'ladi va TypeError: callback is not a function xatosi chiqadi — u ham asinxron, ya'ni ushlab bo'lmaydigan joyda. Kelishuvga rioya qilish — "callback doim oxirida" — bunday chalkashlikdan saqlaydi.
3. Asinxron xatolar
3.1 try/catch nega ushlamaydi
Oshxona yopiq bo'lsa-chi? Birinchi fikr — try/catch:
try {
setTimeout(() => {
throw new Error("Oshxona yopiq");
}, 100);
} catch (xato) {
console.log("Ushlandi:", xato.message);
}
console.log("try tugadi");Node 24 da (C:\... — yo'l qisqartirildi):
try tugadi
file:///C:/.../11/xato.mjs:3
throw new Error("Oshxona yopiq");
^
Error: Oshxona yopiq
at Timeout._onTimeout (file:///C:/.../11/xato.mjs:3:11)
at listOnTimeout (node:internal/timers:685:17)
at process.processTimers (node:internal/timers:618:7)
Node.js v24.21.0"Ushlandi" chiqmadi, dastur esa xato bilan to'xtadi. Sababini stack trace aytib turibdi (Stack trace): pastdan tepaga — processTimers, listOnTimeout, Timeout._onTimeout. Global kod ham, try bloki ham stekda yo'q. Ular 100 ms oldin tugagan: "try tugadi" allaqachon chiqqan.
try/catch faqat o'z bloki bajarilayotgan paytda stekda yuz bergan xatoni ushlaydi. Asinxron callback esa butunlay boshqa paytda, yangi stekda ishlaydi. Ushlaydigan hech kim qolmagan.
3.2 Error-first callback
Demak asinxron xatoni throw bilan emas, callback orqali yetkazish kerak. Node buning uchun kelishuv o'ylab topgan.
Error-first callback ("avval xato" callback'i) — birinchi argumenti doim xato bo'lgan callback:
- xato bo'lsa —
callback(xatoObyekti); - muvaffaqiyat bo'lsa —
callback(null, natija).
Chaqiruvchi birinchi argumentni tekshiradi:
const menyu = { osh: 35000, manti: 30000 };
function narxniSora(taom, callback) {
setTimeout(() => {
if (!Object.hasOwn(menyu, taom)) {
callback(new Error(`Menyuda yo'q: ${taom}`));
return;
}
callback(null, menyu[taom]);
}, 200);
}
function javobniKorsat(xato, narx) {
if (xato) {
console.log("Xato:", xato.message);
return;
}
console.log(`Narx: ${narx} so'm`);
}
narxniSora("osh", javobniKorsat);
narxniSora("palov", javobniKorsat);Konsolda:
Narx: 35000 so'm
Xato: Menyuda yo'q: palovKelishuvning to'rt qoidasi:
- Xato — birinchi. Uni e'tiborsiz qoldirib bo'lmaydi: parametrlar ro'yxatida ko'zga birinchi tashlanadi.
- Muvaffaqiyatda —
null.undefinedemas, aynannull: "xato yo'q" degan aniq belgi. - Xato —
Errorobyekti. Matn emas: undamessage,stackva kerak bo'lsacodebor (throwvaErrorobyekti). - Xatoni berganingizdan keyin —
return. Aks holda kod davom etib, callback'ni ikkinchi marta chaqiradi. Bu eng ko'p uchraydigan xato — uni «Ko'p uchraydigan xatolar» da ko'ramiz.
Object.hasOwn — Object metodlari darsidan: kalit obyektning o'zida bormi. taom in menyu yozilsa, "toString" kabi meros kalitlar ham "bor" bo'lib chiqardi.
3.3 Node'da: haqiqiy error-first
Node'ning callback'li API'lari aynan shu kelishuvda. O'tgan darsdagi readFile ning (xato, matn) parametrlari endi tushunarli. Yo'q faylni o'qiymiz:
import { readFile } from "node:fs";
readFile("bronlar.json", "utf8", (xato, matn) => {
if (xato) {
console.log("O'qib bo'lmadi:", xato.code);
return;
}
console.log(matn);
});Konsolda:
O'qib bo'lmadi: ENOENTxato.code — Node xatolarining qisqa kodi. ENOENT — "Error: NO ENTry", ya'ni "bunday yozuv yo'q": fayl topilmadi. xato.message da esa faylning to'liq yo'li bilan inglizcha matn bor: ENOENT: no such file or directory, open '...\bronlar.json' — "bunday fayl yoki papka yo'q". Kodni tekshirishda doim code ga qarang — u o'zgarmaydi, matn esa o'zgarishi mumkin.
Tekshirib ko'ring: Nega error-first kelishuvida natija birinchi, xato ikkinchi emas?
Javob
Xato birinchi bo'lsa, uni "unutish" qiyin: callback yozayotganda birinchi parametr nomini yozasiz va beixtiyor "xato bormi?" deb o'ylaysiz. Bundan tashqari, natija bir nechta qiymat bo'lishi mumkin (callback(null, a, b)), xato esa doim bitta — uni boshda turgani qulay.
4. Callback hell
4.1 Bir-biriga bog'liq ishlar
Bitta asinxron ish — muammo yo'q. Lekin hayotda ishlar zanjir bo'ladi va har keyingisi oldingisining natijasiga muhtoj. «Bahor»da bron:
- 4 kishilik bo'sh stol topish;
- topilgan stolga bron qilish;
- bronga buyurtma qo'shish;
- buyurtma bo'yicha chek yozish.
Har biri asinxron (server so'rovi, baza) va error-first. Ketma-ket bajaramiz:
function stolTop(odam, callback) {
setTimeout(() => {
if (odam > 20) {
callback(new Error("Bunday katta stol yo'q"));
return;
}
callback(null, 5);
}, 100);
}
function bronQil(stol, callback) {
setTimeout(() => callback(null, `${stol}-stol, 19:00`), 100);
}
function buyurtmaBer(bron, callback) {
setTimeout(() => callback(null, `${bron}, osh`), 100);
}
function chekYoz(buyurtma, callback) {
setTimeout(() => callback(null, `${buyurtma}: 35000 so'm`), 100);
}
stolTop(4, (xato, stol) => {
if (xato) {
console.log("Xato:", xato.message);
return;
}
bronQil(stol, (xato, bron) => {
if (xato) {
console.log("Xato:", xato.message);
return;
}
buyurtmaBer(bron, (xato, buyurtma) => {
if (xato) {
console.log("Xato:", xato.message);
return;
}
chekYoz(buyurtma, (xato, chek) => {
if (xato) {
console.log("Xato:", xato.message);
return;
}
console.log("Chek:", chek);
});
});
});
});Konsolda:
Chek: 5-stol, 19:00, osh: 35000 so'mKod ishlaydi. Lekin pastki yarmiga qarang: u o'ngga suriladi va uchburchak — piramida hosil qiladi. Shunday kod callback hell ("callback do'zaxi") yoki pyramid of doom ("halokat piramidasi") deb ataladi. To'rt qadam — to'rt daraja. Real ilovada qadamlar o'nta bo'ladi.
Har darajada (xato, ...) parametri tashqaridagi xato ni soyalaydi (soyalash). Bu yerda zarari yo'q — har daraja faqat o'z xatosini tekshiradi.
4.2 Piramida nima uchun yomon
Gap faqat chiroyda emas. Callback hell'ning to'rt real muammosi bor:
| Muammo | Piramidada qanday ko'rinadi |
|---|---|
| O'qish qiyin | ish tartibi yuqoridan pastga emas, ichkariga qarab |
| Xato tekshiruvi takrorlanadi | to'rt marta bir xil if (xato) |
| O'zgartirish xavfli | qadam qo'shish — qavslarni qayta sanash |
| Murakkab tartib deyarli imkonsiz | "ikkitasini parallel, keyin uchinchisini" yoki "3 marta qayta urin" |
Oxirgi qator eng og'iri. Ikki stolni bir vaqtda tekshirib, ikkalasi tugaganda davom etish kerak bo'lsa — callback'lar bilan hisoblagichlar, bayroqlar va ko'p if kerak bo'ladi.
Piramidaning tuzilishini diagrammada ko'ring: har qadamdan ikki yo'l ketadi, xato yo'llari esa to'rt marta takrorlanadi.
flowchart TD
A["stolTop(4)"] -->|"stol"| B["bronQil(stol)"]
B -->|"bron"| C["buyurtmaBer(bron)"]
C -->|"buyurtma"| D["chekYoz(buyurtma)"]
D -->|"chek"| E["Chek: ..."]
A -->|"xato"| X["Xato: ..."]
B -->|"xato"| X
C -->|"xato"| X
D -->|"xato"| X4.3 Parallel ishlar: hisoblagich bilan
Jadvalning oxirgi qatorini sinab ko'ramiz. «Bahor»da ikki zal bor — ichki zal va ayvon. Ikkalasidan bo'sh stollarni bir vaqtda so'raymiz (ketma-ket so'rash vaqtni ikki barobar oshiradi). Javoblar har xil vaqtda keladi. Ikkalasi kelgandagina natijani ko'rsatish kerak:
function stolBormi(zal, callback) {
const kechikish = zal === "ichki zal" ? 300 : 100;
setTimeout(() => callback(null, `${zal}: 2 ta bo'sh`), kechikish);
}
const zallar = ["ichki zal", "ayvon"];
const javoblar = [];
let qoldi = zallar.length;
zallar.forEach((zal, indeks) => {
stolBormi(zal, (xato, javob) => {
javoblar[indeks] = javob;
qoldi--;
if (qoldi === 0) {
console.log("Hammasi keldi:", javoblar.join("; "));
}
});
});
console.log("Ikkala so'rov yuborildi");Konsolda:
Ikkala so'rov yuborildi
Hammasi keldi: ichki zal: 2 ta bo'sh; ayvon: 2 ta bo'shAyvon birinchi javob berdi (100 ms), ichki zal — keyin (300 ms). Biz esa uchta narsani qo'lda boshqardik:
qoldihisoblagichi — "yana nechta javob kutyapmiz";javoblar[indeks]— javoblar kelish tartibida emas, so'rov tartibida tursin;- yakuniy ish faqat
qoldi === 0da.
Xatoni ham hisobga olsak, yana bitta bayroq kerak: birinchi xatoda yakuniy callback chaqirilsin, keyingi javoblar esa e'tiborsiz qolsin. Har parallel ish uchun shu kodni qaytadan yozish — xatolar manbai. Promise kombinatorlari darsida buning hammasini bitta qator bajaradi.
4.4 Tekislash: nomli funksiyalar
Callback'larning o'zidan voz kechmasdan ham piramidani tekislash mumkin: har ichki callback'ga nom berib, uni alohida funksiyaga chiqaramiz. Xato tekshiruvini esa bitta funksiyaga yig'amiz:
function stolTop(odam, callback) {
setTimeout(() => callback(null, 5), 100);
}
function bronQil(stol, callback) {
setTimeout(() => callback(null, `${stol}-stol, 19:00`), 100);
}
function buyurtmaBer(bron, callback) {
setTimeout(() => callback(null, `${bron}, osh`), 100);
}
function chekYoz(buyurtma, callback) {
setTimeout(() => callback(null, `${buyurtma}: 35000 so'm`), 100);
}
function xatoniKorsat(xato) {
console.log("Xato:", xato.message);
}
function stolTopildi(xato, stol) {
if (xato) return xatoniKorsat(xato);
bronQil(stol, bronQilindi);
}
function bronQilindi(xato, bron) {
if (xato) return xatoniKorsat(xato);
buyurtmaBer(bron, buyurtmaBerildi);
}
function buyurtmaBerildi(xato, buyurtma) {
if (xato) return xatoniKorsat(xato);
chekYoz(buyurtma, chekYozildi);
}
function chekYozildi(xato, chek) {
if (xato) return xatoniKorsat(xato);
console.log("Chek:", chek);
}
stolTop(4, stolTopildi);Konsolda:
Chek: 5-stol, 19:00, osh: 35000 so'mif (xato) return xatoniKorsat(xato); — bitta qatorli shart: xatoni ko'rsatadi va shu zahoti funksiyadan chiqadi (erta qaytish).
Piramida yo'qoldi, har funksiya qisqa va nomi nima qilishini aytadi. Lekin muammolarning yarmi qoldi: xato tekshiruvi hamon har funksiyada, ish tartibini bilish uchun esa funksiyadan funksiyaga "sakrab" o'qish kerak. Funksiya nomlari ham o'tgan zamonda — stolTopildi "stol topilganda nima qilish kerak" degani. Bu hodisa tinglovchilariga o'xshaydi, ish ketma-ketligiga emas.
Haqiqiy yechim — natijani callback'ga emas, obyektga qaytarish: "kelajakdagi natija" obyekti. U Promise deyiladi va ikki darsdan keyin, Promise asoslari da boshlanadi. U bilan bu to'rt qadam to'rt tekis qatorga aylanadi. Hozircha bilish shart emas — avval navbat qanday ishlashini Event loop darsida ko'ramiz.
Tekshirib ko'ring:
stolTop(25, stolTopildi)chaqirilsa,bronQilishlaydimi? (AslstolTop— 20 dan ko'p odamga xato beradigan versiya.)
Javob
Yo'q. stolTop callback(new Error("Bunday katta stol yo'q")) ni chaqiradi. stolTopildi da xato bor — xatoniKorsat ishlaydi va return funksiyadan chiqadi. bronQil qatoriga yetib bormaydi. Konsolda: Xato: Bunday katta stol yo'q.
5. Inversion of control
5.1 Boshqaruvni topshirish
Sinxron kodda boshqaruv sizda: funksiyani chaqirasiz, u qaytadi, davom etasiz. Callback berganingizda esa davomini boshqa kodga topshirasiz: "ish tugaganda shuni chaqir". Bu inversion of control (boshqaruvni teskari topshirish) deyiladi. Xuddi uy kalitini ta'mirchiga qoldirib ketgandek: ish bitadi, lekin u eshikni qachon va necha marta ochishini ko'rmaysiz.
O'z funksiyangiz bo'lsa — xavotir yo'q. Lekin callback ko'pincha begona kodga beriladi: kutubxonaga, to'lov tizimiga, boshqa jamoaning moduliga. Ularning ichini ko'rmaysiz. Va «Callback — shartnoma» bo'limidagi har bandni ular buzishi mumkin.
sequenceDiagram
participant S as Sizning kod
participant K as To'lov kutubxonasi
S->>K: tolovQil(35000, callback)
K-->>S: qaytdi (natija hali yo'q)
Note over S: Davomi endi kutubxona qo'lida
K->>S: callback(null, "to'landi")
K->>S: callback(null, "to'landi") — yana!5.2 Ikki marta chaqirish
To'lov kutubxonasida xato bor: u callback'ni ikki marta chaqiradi.
// Begona kutubxona — ichida xato bor, lekin siz buni bilmaysiz
function tolovQil(summa, callback) {
setTimeout(() => callback(null, "to'landi"), 100);
setTimeout(() => callback(null, "to'landi"), 150);
}
let tushum = 0;
tolovQil(35000, (xato, holat) => {
tushum += 35000;
console.log(`To'lov: ${holat}, tushum: ${tushum} so'm`);
});Konsolda:
To'lov: to'landi, tushum: 35000 so'm
To'lov: to'landi, tushum: 70000 so'mDilshod aka bitta osh uchun ikki marta "to'ladi" — hisobotda 70 000. Kutubxonani tuzata olmaysiz, lekin callback'ingizni himoya qila olasiz. Closure amaliyotda dagi once g'oyasi — callback faqat birinchi marta ishlasin:
function tolovQil(summa, callback) {
setTimeout(() => callback(null, "to'landi"), 100);
setTimeout(() => callback(null, "to'landi"), 150);
}
function birMarta(callback) {
let chaqirildi = false;
return (...argumentlar) => {
if (chaqirildi) {
console.log("Diqqat: callback qayta chaqirildi, o'tkazildi");
return;
}
chaqirildi = true;
callback(...argumentlar);
};
}
let tushum = 0;
tolovQil(35000, birMarta((xato, holat) => {
tushum += 35000;
console.log(`To'lov: ${holat}, tushum: ${tushum} so'm`);
}));Konsolda:
To'lov: to'landi, tushum: 35000 so'm
Diqqat: callback qayta chaqirildi, o'tkazildibirMarta callback'ni o'raydi: chaqirildi bayrog'i closure'da yashaydi. ...argumentlar — rest parametr: kutubxona nechta argument bersa ham, hammasi asl callback'ga uzatiladi.
5.3 Shartnomaning boshqa buzilishlari
Ikki marta chaqirish — yagona xavf emas. Begona kod yana:
- umuman chaqirmasligi mumkin — sizning ilovangiz abadiy "Yuklanmoqda..." holatida qoladi. Himoya — vaqt chegarasi: belgilangan vaqtda javob kelmasa, o'zingiz xato chiqarasiz (3-mashqda yozasiz);
- xatoni yutib yuborishi mumkin — xato bo'lsa ham
callback(null, undefined)beradi; - noto'g'ri argument berishi mumkin — natijani birinchi o'ringa qo'yib;
- goh sinxron, goh asinxron chaqirishi mumkin. Bu eng ayyor xato — keyingi bo'limda.
Har birini himoyalash — ortiqcha kod. Bu ham callback'larning narxi. Promise bu kafolatlarning ko'pini tilning o'zi beradi: aynan bir marta, doim asinxron, xato alohida yo'ldan.
5.4 Goh sinxron, goh asinxron
Sardor narx so'rovini tezlashtirmoqchi: bir marta kelgan narxni Map keshda saqlaydi va keyingi safar darhol beradi:
const kesh = new Map();
function narxniOl(taom, callback) {
if (kesh.has(taom)) {
callback(kesh.get(taom));
return;
}
setTimeout(() => {
kesh.set(taom, 35000);
callback(35000);
}, 100);
}
function sotibOl(nomi) {
console.log(`${nomi}: so'rov`);
narxniOl("osh", (narx) => console.log(`${nomi}: narx ${narx}`));
console.log(`${nomi}: so'rovdan keyingi qator`);
}
sotibOl("1-mehmon");
setTimeout(sotibOl, 300, "2-mehmon");Konsolda:
1-mehmon: so'rov
1-mehmon: so'rovdan keyingi qator
1-mehmon: narx 35000
2-mehmon: so'rov
2-mehmon: narx 35000
2-mehmon: so'rovdan keyingi qatorBir xil kod — ikki xil tartib! Birinchi safar callback asinxron (keshda yo'q), ikkinchi safar — sinxron (keshdan darhol). sotibOl yozgan dasturchi "narx keyingi qatordan keyin keladi" deb o'ylasa, ikkinchi mehmonda kod buziladi. Bunday xatolarni topish juda qiyin: ular faqat ma'lum holatda — kesh to'lganda — chiqadi.
Node jamoasida bu muammo hazil bilan "Zalgo" deb atalgan. Zalgo — internetdagi hazil "yovuz maxluq", tartibsizlik ramzi. "Zalgo'ni qo'yib yubormang" — "oldindan bilib bo'lmaydigan xulqqa yo'l qo'ymang" degani. Qoida: callback doim asinxron yoki doim sinxron bo'lsin — hech qachon aralash emas. Tuzatish — keshdagi javobni ham kechiktirish:
if (kesh.has(taom)) {
setTimeout(() => callback(kesh.get(taom)), 0);
return;
}Endi ikkala mehmonda ham tartib bir xil: "so'rov", "keyingi qator", "narx". Event loop darsida buni tezroq qiladigan vosita — queueMicrotask ni ko'ramiz.
Tekshirib ko'ring: Nega "Zalgo" xatosi sinovda ko'pincha ko'rinmaydi?
Javob
Sinovda odatda bitta so'rov yuboriladi — kesh bo'sh, callback asinxron va hammasi to'g'ri ishlaydi. Sinxron yo'l faqat ikkinchi va keyingi so'rovlarda, kesh to'lganda ochiladi. Xato real foydalanuvchida, "tasodifan" chiqadi.
6. Ko'p uchraydigan xatolar
6.1 Xatodan keyin return ni unutish
function bronQil(odam, callback) {
if (odam > 20) {
callback(new Error("20 kishidan ko'p bo'lmaydi"));
}
callback(null, `${odam} kishilik bron`);
}
bronQil(25, (xato, bron) => {
console.log(xato ? `Xato: ${xato.message}` : bron);
});Konsolda:
Xato: 20 kishidan ko'p bo'lmaydi
25 kishilik bronCallback ikki marta chaqirildi: avval xato bilan, keyin "muvaffaqiyat" bilan. Mehmon "bron bo'lmadi" va "bron bo'ldi" degan ikki xabar oladi. Tuzatish: callback(xato) dan keyin darhol return; — yoki bir qatorda return callback(xato);.
6.2 Xatoni tekshirmaslik
readFile("menyu.txt", "utf8", (xato, matn) => console.log(matn.length)) — fayl yo'q bo'lsa, matn — undefined, va undefined.length dan TypeError: Cannot read properties of undefined (reading 'length') chiqadi. Asl sabab (ENOENT) yo'qoladi, siz esa boshqa xatoni tuzatishga urinasiz. Tuzatish: error-first callback'ning birinchi qatori — doim if (xato).
6.3 Callback ichidagi throw
Asinxron callback ichida throw — ushlab bo'lmaydigan xato: Node'da dastur to'xtaydi, brauzerda konsolga Uncaught Error chiqadi. Tuzatish: asinxron funksiyada xatoni callback(new Error(...)) bilan bering.
6.4 Natijani tashqariga "olib chiqish"
let narx;
narxniSora("osh", (xato, n) => {
narx = n;
});
console.log(narx); // undefinedO'tgan darslardagi xatoning callback ko'rinishi. Callback hali ishlamagan. Tuzatish: narxga bog'liq kod callback ichida (yoki callback chaqiradigan funksiyada) bo'lsin.
7. Mashqlar
1-mashq (oson): Error-first bron
stolniBronQil(odam, callback) funksiyasini yozing. 200 ms dan keyin: odam 1 dan 20 gacha bo'lsa — callback(null, "<odam> kishilik stol bron qilindi"), aks holda — callback(new Error("Odam soni 1–20 oralig'ida bo'lsin")). 3 va 0 bilan sinang.
Yechim
function stolniBronQil(odam, callback) {
setTimeout(() => {
if (odam < 1 || odam > 20) {
callback(new Error("Odam soni 1–20 oralig'ida bo'lsin"));
return;
}
callback(null, `${odam} kishilik stol bron qilindi`);
}, 200);
}
function natijaniYoz(xato, xabar) {
if (xato) {
console.log("Xato:", xato.message);
return;
}
console.log(xabar);
}
stolniBronQil(3, natijaniYoz);
stolniBronQil(0, natijaniYoz);Konsolda:
3 kishilik stol bron qilindi
Xato: Odam soni 1–20 oralig'ida bo'lsinIkkala chaqiruv ham 200 ms ga qo'yilgan — taymerlar qo'yilgan tartibda ishladi. return xato yo'lidan keyin turibdi — callback faqat bir marta chaqiriladi.
2-mashq (o'rta): Piramidani tekislang
Ko'k choy damlash uch qadamli. Piramidani nomli funksiyalar bilan tekis qilib qayta yozing. Chiqish o'zgarmasin.
function suvQaynat(callback) {
setTimeout(() => callback(null, "qaynoq suv"), 100);
}
function choySol(suv, callback) {
setTimeout(() => callback(null, `${suv} + ko'k choy`), 100);
}
function damla(choynak, callback) {
setTimeout(() => callback(null, `${choynak} (damlandi)`), 100);
}
suvQaynat((xato, suv) => {
if (xato) return console.log("Xato:", xato.message);
choySol(suv, (xato, choynak) => {
if (xato) return console.log("Xato:", xato.message);
damla(choynak, (xato, choy) => {
if (xato) return console.log("Xato:", xato.message);
console.log("Tayyor:", choy);
});
});
});Konsolda:
Tayyor: qaynoq suv + ko'k choy (damlandi)Yechim
function suvQaynat(callback) {
setTimeout(() => callback(null, "qaynoq suv"), 100);
}
function choySol(suv, callback) {
setTimeout(() => callback(null, `${suv} + ko'k choy`), 100);
}
function damla(choynak, callback) {
setTimeout(() => callback(null, `${choynak} (damlandi)`), 100);
}
function xatoniYoz(xato) {
console.log("Xato:", xato.message);
}
function suvQaynadi(xato, suv) {
if (xato) return xatoniYoz(xato);
choySol(suv, choySolindi);
}
function choySolindi(xato, choynak) {
if (xato) return xatoniYoz(xato);
damla(choynak, damlandi);
}
function damlandi(xato, choy) {
if (xato) return xatoniYoz(xato);
console.log("Tayyor:", choy);
}
suvQaynat(suvQaynadi);Konsolda:
Tayyor: qaynoq suv + ko'k choy (damlandi)Oxirgi qator — butun jarayonning boshlanishi: suvQaynat(suvQaynadi). Har nomli funksiya "shu qadam tugaganda nima qilish kerak" degan savolga javob beradi.
3-mashq (qiyin): Ketma-ket bajaruvchi va vaqt chegarasi
kurs/mashqlar/11/03-callbacklar/ketmaKet.mjs faylida ikki funksiya yozing.
muddatBilan(callback, ms)—callbackni o'raydi: agarmsichida chaqirilmasa, o'zicallback(new Error("Javob kelmadi"))ni chaqiradi; kech kelgan javob esa e'tiborsiz qoladi. Ichida —setTimeout,clearTimeoutva «Inversion of control» dagibirMartag'oyasi.ketmaKet(ishlar, yakuniy)—ishlar— error-first funksiyalar massivi, har biri(callback)oladi. Ular birin-ketin bajarilsin; natijalar massivga yig'ilsin. Hammasi tugasa —yakuniy(null, natijalar). Biror ish xato bersa — qolganlari bajarilmasin vayakuniy(xato). Har ishmuddatBilan(..., 300)bilan himoyalansin.
Sinov: uch ish — "osh" (100 ms), "manti" (100 ms) va hech qachon javob bermaydigan "lag'mon".
Yechim
function muddatBilan(callback, ms) {
let tugadi = false;
const taymer = setTimeout(() => {
tugadi = true;
callback(new Error("Javob kelmadi"));
}, ms);
return (...argumentlar) => {
if (tugadi) {
return;
}
tugadi = true;
clearTimeout(taymer);
callback(...argumentlar);
};
}
function ketmaKet(ishlar, yakuniy) {
const natijalar = [];
function keyingisi(indeks) {
if (indeks === ishlar.length) {
return yakuniy(null, natijalar);
}
ishlar[indeks](muddatBilan((xato, natija) => {
if (xato) return yakuniy(xato);
natijalar.push(natija);
keyingisi(indeks + 1);
}, 300));
}
keyingisi(0);
}
const tayyorla = (taom) => (callback) => {
setTimeout(() => callback(null, `${taom} tayyor`), 100);
};
const lagmon = (callback) => {
console.log("lag'mon: oshpaz javob bermayapti...");
};
ketmaKet([tayyorla("osh"), tayyorla("manti")], (xato, n) => {
console.log("1-buyurtma:", xato ? xato.message : n.join(", "));
ketmaKet([tayyorla("osh"), lagmon, tayyorla("manti")], (x) => {
console.log("2-buyurtma:", x ? x.message : "tayyor");
});
});Konsolda:
1-buyurtma: osh tayyor, manti tayyor
lag'mon: oshpaz javob bermayapti...
2-buyurtma: Javob kelmadiIkkinchi buyurtmada "osh" tayyor bo'ldi, keyin lagmon hech qachon callback chaqirmadi. 300 ms dan keyin muddatBilan o'zi xato berdi va ketmaKet to'xtadi — "manti" boshlanmadi ham. Bu bo'lmasa, ilova abadiy kutardi.
Yordamchi tayyorla — funksiya qaytaradigan funksiya (Closure amaliyotda dagi fabrika). Masalan, tayyorla("osh") — tayyor ish: uni keyinroq (callback) bilan chaqirish mumkin. ketmaKet ichidagi keyingisi o'zini callback ichidan chaqiradi — bu rekursiyaga o'xshaydi, lekin stek o'smaydi: har chaqiruv yangi taymer callback'idan, bo'sh stekdan boshlanadi.
git add 11/03-callbacklar/ketmaKet.mjs
git commit -m "11/03: ketmaKet va muddatBilan"8. Real ishda
- Node API'lari.
fs,crypto,zlib,child_processning callback'li variantlari hamon bor va error-first kelishuvida. Zamonaviy kodda ularningPromisevariantlari (node:fs/promises) ishlatiladi. Callback'li funksiyani Promise'ga aylantirishni Promise yordamchilari va promisify darsida ko'ramiz. - Brauzer. Hodisa tinglovchilari,
setTimeout, eskiXMLHttpRequest— callback'li. Ko'p marta yuz beradigan narsalar (bosish, xabar) uchun callback hamon to'g'ri vosita. - Kutubxonalar. Eski jQuery (
$.ajax({ success, error })), Express'ningnext(xato)— callback g'oyasining turli shakllari. Express'ni 25-qismda ko'ramiz. - Kod sharhi (code review). "Callback ikki marta chaqirilishi mumkinmi?", "Xatodan keyin
returnbormi?" — tajribali dasturchi callback'li kodda birinchi shuni qidiradi. - Intervyu. "Callback hell nima va uni qanday hal qilasiz?", "Error-first callback nima?", "Inversion of control nima?" — Promise'dan oldingi klassik savollar.
Xulosa
- Asinxron natija callback orqali keladi: funksiya darhol qaytadi, callback keyinroq natija bilan chaqiriladi.
try/catchasinxron callback xatosini ushlamaydi — callback boshqa paytda, yangi stekda ishlaydi.- Error-first:
callback(xato)yokicallback(null, natija); chaqiruvchi avvalif (xato)ni tekshiradi; xatodan keyin —return. - Bog'liq asinxron ishlar ichma-ich callback'larga — callback hell piramidasiga aylanadi; nomli funksiyalar uni tekislaydi, lekin muammoni to'liq hal qilmaydi.
- Inversion of control: callback'ni begona kodga berib, uning qachon va necha marta chaqirilishini nazorat qilmaysiz. Himoya —
birMarta, vaqt chegarasi va "doim asinxron" qoidasi.
Keyingi dars: Event loop — stek, Web API'lar, task va microtask navbatlari: asinxron kod qaysi tartibda bajarilishini aniq oldindan aytishni o'rganamiz.
Manbalar
- Node.js hujjatlari: "Error-first callbacks", "Errors" (
error.code, ENOENT) — nodejs.org - MDN: "Callback function" (Glossary), "Introducing asynchronous JavaScript" — developer.mozilla.org
- Isaac Z. Schlueter, "Designing APIs for Asynchrony" (Zalgo) — blog.izs.me
- Kyle Simpson, "You Don't Know JS: Async & Performance", 2-bob — "Callbacks", inversion of control
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!