IlmHamroh
JavaScript Full-stack/11-qism. Asinxron JavaScript va tarmoq3/32-dars22 daqiqa
Mundarija (32)

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 yoki null. 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/catch ushlamasligini 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:

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

text
Oshxonadan so'raldi
Kutish davomida choy quyildi
Manti: 30000 so'm

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

js
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 uni stolniTop(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:

js
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):

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

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

text
Narx: 35000 so'm
Xato: Menyuda yo'q: palov

Kelishuvning to'rt qoidasi:

  1. Xato — birinchi. Uni e'tiborsiz qoldirib bo'lmaydi: parametrlar ro'yxatida ko'zga birinchi tashlanadi.
  2. Muvaffaqiyatda — null. undefined emas, aynan null: "xato yo'q" degan aniq belgi.
  3. Xato — Error obyekti. Matn emas: unda message, stack va kerak bo'lsa code bor (throw va Error obyekti).
  4. 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:

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

text
O'qib bo'lmadi: ENOENT

xato.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:

  1. 4 kishilik bo'sh stol topish;
  2. topilgan stolga bron qilish;
  3. bronga buyurtma qo'shish;
  4. buyurtma bo'yicha chek yozish.

Har biri asinxron (server so'rovi, baza) va error-first. Ketma-ket bajaramiz:

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

text
Chek: 5-stol, 19:00, osh: 35000 so'm

Kod 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"| X

4.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:

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

text
Ikkala so'rov yuborildi
Hammasi keldi: ichki zal: 2 ta bo'sh; ayvon: 2 ta bo'sh

Ayvon birinchi javob berdi (100 ms), ichki zal — keyin (300 ms). Biz esa uchta narsani qo'lda boshqardik:

  • qoldi hisoblagichi — "yana nechta javob kutyapmiz";
  • javoblar[indeks] — javoblar kelish tartibida emas, so'rov tartibida tursin;
  • yakuniy ish faqat qoldi === 0 da.

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:

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

text
Chek: 5-stol, 19:00, osh: 35000 so'm

if (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, bronQil ishlaydimi? (Asl stolTop — 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.

js
// 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:

text
To'lov: to'landi, tushum: 35000 so'm
To'lov: to'landi, tushum: 70000 so'm

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

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

text
To'lov: to'landi, tushum: 35000 so'm
Diqqat: callback qayta chaqirildi, o'tkazildi

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

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

text
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 qator

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

js
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

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

text
Xato: 20 kishidan ko'p bo'lmaydi
25 kishilik bron

Callback 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"

js
let narx;
narxniSora("osh", (xato, n) => {
  narx = n;
});
console.log(narx); // undefined

O'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
js
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:

text
3 kishilik stol bron qilindi
Xato: Odam soni 1–20 oralig'ida bo'lsin

Ikkala 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.

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

text
Tayyor: qaynoq suv + ko'k choy (damlandi)
Yechim
js
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:

text
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.

  1. muddatBilan(callback, ms) — callback ni o'raydi: agar ms ichida chaqirilmasa, o'zi callback(new Error("Javob kelmadi")) ni chaqiradi; kech kelgan javob esa e'tiborsiz qoladi. Ichida — setTimeout, clearTimeout va «Inversion of control» dagi birMarta g'oyasi.
  2. 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 va yakuniy(xato). Har ish muddatBilan(..., 300) bilan himoyalansin.

Sinov: uch ish — "osh" (100 ms), "manti" (100 ms) va hech qachon javob bermaydigan "lag'mon".

Yechim
js
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:

text
1-buyurtma: osh tayyor, manti tayyor
lag'mon: oshpaz javob bermayapti...
2-buyurtma: Javob kelmadi

Ikkinchi 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.

bash
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_process ning callback'li variantlari hamon bor va error-first kelishuvida. Zamonaviy kodda ularning Promise variantlari (node:fs/promises) ishlatiladi. Callback'li funksiyani Promise'ga aylantirishni Promise yordamchilari va promisify darsida ko'ramiz.
  • Brauzer. Hodisa tinglovchilari, setTimeout, eski XMLHttpRequest — callback'li. Ko'p marta yuz beradigan narsalar (bosish, xabar) uchun callback hamon to'g'ri vosita.
  • Kutubxonalar. Eski jQuery ($.ajax({ success, error })), Express'ning next(xato) — callback g'oyasining turli shakllari. Express'ni 25-qismda ko'ramiz.
  • Kod sharhi (code review). "Callback ikki marta chaqirilishi mumkinmi?", "Xatodan keyin return bormi?" — 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/catch asinxron callback xatosini ushlamaydi — callback boshqa paytda, yangi stekda ishlaydi.
  • Error-first: callback(xato) yoki callback(null, natija); chaqiruvchi avval if (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
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript callback'lari va callback hell: asinxron natijani olish — IlmHamroh