IlmHamroh
JavaScript Full-stack/11-qism. Asinxron JavaScript va tarmoq6/32-dars23 daqiqa
Mundarija (31)

JavaScript Promise zanjiri: then qaytaradigan yangi Promise

Qisqacha: then doim yangi Promise qaytaradi. Callback qiymat qaytarsa — yangi Promise shu qiymat bilan bajariladi; Promise qaytarsa — zanjir uni kutadi; throw qilsa — rad etiladi. Shuning uchun asinxron qadamlarni ichma-ich emas, tekis yozish mumkin: stolTop(4).then(bronQil).then(chekYoz).catch(...). Xato zanjir bo'ylab pastga — eng yaqin catch gacha — oqadi.

Bu darsda

  • then nima qaytarishini va keyingi then qaysi qiymatni olishini aniq ayta olasiz.
  • Callback hell piramidasini tekis Promise zanjiriga aylantira olasiz.
  • Xato zanjir bo'ylab qanday tarqalishini, catch dan keyin zanjir qanday davom etishini tushuntira olasiz.
  • return ni unutish va ichma-ich then xatolarini tanib, tuzata olasiz.
  • Ikki zanjir aralash bo'lganda chiqish tartibini microtask navbati bo'yicha ayta olasiz.

Oldin bilishingiz kerak: Promise asoslari, Callbacklar va callback hell, Event loop.

1. Nega bu kerak?

Callbacklar va callback hell darsida «Bahor»dagi bron to'rt qadamdan iborat edi: stol topish, bron qilish, buyurtma berish, chek yozish. Har qadam oldingisining natijasiga muhtoj, va kod o'ngga surilib, piramidaga aylangan edi. O'shanda "Promise bilan bu to'rt qadam to'rt tekis qatorga aylanadi" degan edik.

Promise asoslari da bitta asinxron ishni Promise bilan yozishni o'rgandik. Lekin to'rt qadamni-chi? Birinchi fikr — har then ichida keyingi funksiyani chaqirish:

js
stolTop(4).then((stol) => {
  bronQil(stol).then((bron) => {
    buyurtmaBer(bron).then((buyurtma) => {
      // ...
    });
  });
});

Bu yana o'sha piramida, faqat then bilan. Promise'ning haqiqiy kuchi boshqa joyda: then o'zi ham Promise qaytaradi. Bugun shu bitta fakt piramidani qanday yo'q qilishini ko'ramiz.

O'xshatish. «Bahor»da osh tayyorlash — konveyer: sabzi to'g'raladi, keyin qovuriladi, keyin guruch solinadi. Har bosqichdagi oshpaz oldingisidan tayyor narsani oladi va o'z ishini qilib, keyingisiga uzatadi. Hech kim boshqasining ichiga kirmaydi — har biri faqat "kirdi" va "chiqdi" ni biladi. Promise zanjiri — xuddi shunday konveyer.

2. then yangi Promise qaytaradi

2.1 Ikkita har xil Promise

Tekshirib ko'ramiz: then nima qaytaradi?

js
const narx = new Promise((resolve) => resolve(35000));
const ikkiPorsiya = narx.then((n) => n * 2);

console.log(narx === ikkiPorsiya);
console.log(ikkiPorsiya);
setTimeout(() => console.log(ikkiPorsiya), 0);

Konsolda:

text
false
Promise { <pending> }
Promise { 70000 }

Uchta narsani ko'ramiz:

  • ikkiPorsiya — narx ning o'zi emas, boshqa Promise.
  • Darhol chiqarganimizda u hali kutilmoqda. then callback'i microtask'da ishlaydi (Promise asoslari) — sinxron kod paytida u hali bajarilmagan.
  • Uchinchi qatorni setTimeout(..., 0) bilan keyinga surdik. Taymer callback'i — task, u microtask'lardan keyin ishlaydi (Event loop). Shu paytga kelib callback ishlab bo'lgan va ikkiPorsiya uning qaytargan qiymati bilan bajarilgan: 35000 * 2, ya'ni 70000.

Demak then callback'idagi return — oddiy return emas. U keyingi Promise'ning natijasi bo'ladi.

2.2 Qiymat qaytarish: konveyer

Natija yangi Promise'da bo'lgani uchun unga yana then ulash mumkin. Va yana, va yana:

js
const narx = new Promise((resolve) => resolve(35000));

narx
  .then((n) => n * 2)
  .then((jami) => jami - 5000)
  .then((tolov) => `To'lov: ${tolov} so'm`)
  .then((matn) => console.log(matn));

Konsolda:

text
To'lov: 65000 so'm

Har then oldingisidan qiymat oladi va o'zinikini qaytaradi: 35000 → 70000 (ikki porsiya) → 65000 (5 000 chegirma) → matn. Oxirgi then faqat chiqaradi. U hech narsa qaytarmaydi, shuning uchun uning Promise'i undefined bilan bajariladi — lekin unga hech kim qaramaydi.

Qatorlarning joylashuvi — kelishuv: zanjir boshi alohida qatorda, har then yangi qatorda, ikki bo'shliq ichkarida. Bu bitta uzun ifoda, faqat o'qish uchun bo'lingan.

Tekshirib ko'ring: Zanjirdagi ikkinchi qatorni .then((jami) => { jami - 5000; }) deb o'zgartirsak, oxirida nima chiqadi?

Javob

To'lov: undefined so'm. Jingalak qavslar bilan yozilgan funksiyada return yo'q — u undefined qaytaradi. Keyingi then aynan shu undefined ni oladi. Qavssiz shakl (jami) => jami - 5000 esa qiymatni o'zi qaytaradi.

2.3 Promise qaytarish: zanjir kutadi

Endi eng muhim qoida. Callback Promise qaytarsa, yangi Promise darhol bajarilmaydi. U qaytarilgan Promise hal bo'lishini kutadi va uning natijasini oladi.

Buni ko'rish uchun kichik yordamchi yozamiz — kut(ms): ms millisekunddan keyin bajariladigan Promise.

js
function kut(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

console.log("Choy qo'yildi");
kut(300)
  .then(() => {
    console.log("Suv qaynadi");
    return kut(200);
  })
  .then(() => {
    console.log("Choy damlandi");
    return "ko'k choy";
  })
  .then((choy) => console.log(`Tayyor: ${choy}`));

Konsolda:

text
Choy qo'yildi
Suv qaynadi
Choy damlandi
Tayyor: ko'k choy

setTimeout(resolve, ms) — resolve ning o'zini taymer callback'i qilib beramiz: vaqt kelganda Promise bajariladi. Qiymati undefined, chunki bizga faqat "vaqt o'tdi" degan xabar kerak.

Zanjirga qarang. Birinchi then "Suv qaynadi" deb yozadi va kut(200) ni qaytaradi. Ikkinchi then darhol ishlamaydi: zanjir 200 ms kutadi. Faqat kut(200) bajarilgach "Choy damlandi" chiqadi. Vaqt bo'yicha shunday:

Vaqt Nima bo'ldi
0 ms "Choy qo'yildi", kut(300) taymeri qo'yildi
~300 ms "Suv qaynadi", kut(200) qaytarildi — zanjir kutadi
~500 ms "Choy damlandi", keyin darhol "Tayyor: ko'k choy"

Oxirgi ikki qator orasida kutish yo'q: ikkinchi then Promise emas, oddiy satr qaytardi.

kut — bu qismda tez-tez ishlatadigan yordamchimiz. Uning "rasmiy" ko'rinishi va o'xshash vositalar — Promise yordamchilari darsida.

2.4 Qoida bitta jadvalda

then qaytaradigan yangi Promise'ning taqdiri faqat callback'ga bog'liq:

Callback nima qildi Yangi Promise
qiymat qaytardi (return 70000) shu qiymat bilan bajarildi
hech narsa qaytarmadi undefined bilan bajarildi
Promise qaytardi (return kut(200)) o'sha Promise'ni kutadi, uning natijasini oladi
throw qildi o'sha xato bilan rad etildi

Oxirgi qatorni «Xato zanjir bo'ylab oqadi» bo'limida ko'ramiz.

Tekshirib ko'ring: kut(100).then(() => kut(500)).then(() => console.log("tamom")) — "tamom" taxminan necha millisekunddan keyin chiqadi?

Javob

Taxminan 600 ms. Birinchi then kut(500) ni qaytaradi, shuning uchun zanjir uni kutadi: 100 + 500. Agar return bo'lmasa (() => { kut(500); }), "tamom" 100 ms da chiqardi — buni «Ko'p uchraydigan xatolar» da ko'ramiz.

3. Callback hell → tekis zanjir

3.1 To'rt qadam — to'rt qator

Endi va'dani bajaramiz. To'rt funksiyani Promise qaytaradigan qilib yozamiz va zanjirga ulaymiz:

js
function stolTop(odam) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (odam > 20) reject(new Error("Bunday katta stol yo'q"));
      else resolve(5);
    }, 100);
  });
}
function bronQil(stol) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`${stol}-stol, 19:00`), 100);
  });
}
function buyurtmaBer(bron) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`${bron}, osh`), 100);
  });
}
function chekYoz(buyurtma) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`${buyurtma}: 35000 so'm`), 100);
  });
}

stolTop(4)
  .then((stol) => bronQil(stol))
  .then((bron) => buyurtmaBer(bron))
  .then((buyurtma) => chekYoz(buyurtma))
  .then((chek) => console.log("Chek:", chek))
  .catch((xato) => console.log("Xato:", xato.message));

Konsolda:

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

Callback'li versiyani eslang: to'rt daraja ichkariga, to'rt marta if (xato). Bu yerda — to'rt tekis qator va bitta catch. Har then keyingi funksiyaning Promise'ini qaytaradi, zanjir uni kutadi va natijani pastga uzatadi.

Natija bir xil — Chek: 5-stol, 19:00, osh: 35000 so'm. Ish tartibi ham bir xil: har qadam oldingisi tugagach boshlanadi. Faqat kod endi yuqoridan pastga o'qiladi — xuddi sinxron kod kabi.

3.2 Yanada qisqa

(stol) => bronQil(stol) — "stolni olib, bronQil ga ber". Buni qisqaroq yozish mumkin: funksiyaning o'zini, qavssiz beramiz — Taymerlar darsidagi setTimeout(salomBer, 1000) kabi:

js
stolTop(4)
  .then(bronQil)
  .then(buyurtmaBer)
  .then(chekYoz)
  .then((chek) => console.log("Chek:", chek))
  .catch((xato) => console.log("Xato:", xato.message));

then(bronQil) — then natijani bronQil ning birinchi argumentiga beradi. Funksiya bitta parametr olsa — xavfsiz. Bir nechta parametrli funksiya bilan ehtiyot bo'ling: then faqat bitta argument beradi.

flowchart TD
  A["stolTop(4)"] -->|"stol"| B["then(bronQil)"]
  B -->|"bron"| C["then(buyurtmaBer)"]
  C -->|"buyurtma"| D["then(chekYoz)"]
  D -->|"chek"| E["then: Chek: ..."]
  A -. "rad etildi" .-> X["catch: Xato: ..."]
  B -.-> X
  C -.-> X
  D -.-> X

Diagrammaga qarang: asosiy yo'l — to'g'ri chiziq. Xato bo'lsa, istalgan qadamdan bitta catch ga sakraladi (punktir strelkalar).

3.3 Zanjirni funksiyadan qaytaring

Real kodda bron jarayoni bir joyda yoziladi, natijasi esa boshqa joyda ishlatiladi: bir sahifa chekni ekranga chiqaradi, boshqasi SMS yuboradi. Buning uchun zanjirni funksiyaga o'raymiz va zanjirning o'zini qaytaramiz:

js
function kut(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function bronJarayoni(odam) {
  return kut(100)
    .then(() => {
      if (odam > 20) throw new Error("Bunday katta stol yo'q");
      return 5;
    })
    .then((stol) => `${stol}-stol, ${odam} kishi, 19:00`);
}

bronJarayoni(4)
  .then((bron) => console.log("Ekranda:", bron))
  .catch((xato) => console.log("Ekranda xato:", xato.message));

bronJarayoni(30)
  .then((bron) => console.log("SMS:", bron))
  .catch((xato) => console.log("SMS xato:", xato.message));

Konsolda:

text
Ekranda: 5-stol, 4 kishi, 19:00
SMS xato: Bunday katta stol yo'q

bronJarayoni o'zi catch qilmaydi — u xato bilan nima qilishni bilmaydi. Ekran xatoni qizil matn bilan, SMS xizmati esa boshqacha ko'rsatadi. Shuning uchun funksiya zanjirni return qiladi va qaror chaqiruvchida qoladi. Bu — Xato strategiyasi darsidagi "xatoni ushlay olmasang, yuqoriga uzat" qoidasining asinxron ko'rinishi.

Zanjir boshidagi kut(100) — server so'rovi o'rnida turibdi. Undan keyingi then ichida oddiy sinxron tekshiruv va throw — ular ham zanjirning bir qismi bo'ladi.

Tekshirib ko'ring: Yuqoridagi zanjirni stolTop(25) bilan boshlasak, bronQil chaqiriladimi? Nima chiqadi?

Javob

Yo'q. stolTop(25) rad etiladi. Uchta then va oxirgi then ham o'tkazib yuboriladi, xato to'g'ri catch ga keladi: Xato: Bunday katta stol yo'q.

4. Bajarilish tartibi: har then — alohida microtask

Eslatma: microtask navbati — sinxron kod tugagach, taymerlardan oldin bo'shatiladigan navbat (Event loop). Navbatga birinchi tushgan birinchi ishlaydi.

Zanjirdagi har then callback'i alohida microtask. Keyingisi navbatga faqat oldingisi tugagach qo'yiladi. Ikki zanjir yonma-yon bo'lsa, ular navbatma-navbat ishlaydi. Tartibni oldindan ayting:

js
const osh = new Promise((resolve) => resolve("osh"));
const choy = new Promise((resolve) => resolve("choy"));

osh
  .then((t) => console.log(`1. ${t} tayyor`))
  .then(() => console.log("3. osh suzildi"));
choy
  .then((t) => console.log(`2. ${t} tayyor`))
  .then(() => console.log("4. choy quyildi"));
console.log("0. sinxron kod tugadi");

Konsolda:

text
0. sinxron kod tugadi
1. osh tayyor
2. choy tayyor
3. osh suzildi
4. choy quyildi

Ko'pchilik "osh zanjiri birinchi yozilgan — demak 1 va 3 birga chiqadi" deb o'ylaydi. Yo'q: zanjirlar "aralashib" ketdi. Qadamma-qadam qarang — microtask navbatiga nima qachon tushishini kuzating:

Kalit joy — oltinchi qadam. «3» zanjirda «1» dan keyin turibdi, lekin u navbatga «1» tugagandagina tushadi. Shu paytda «2» allaqachon navbatda turgan edi. Shuning uchun zanjirlar bitta-bitta "qadam tashlaydi".

Bitta nozik detal: callback qiymat emas, Promise qaytarsa, zanjir uni "ochish" uchun yana ikki microtask sarflaydi. Aralash zanjirlarda bu tartibni yana o'zgartiradi. Buni Microtask va macrotask chuqur darsida qadamma-qadam ko'ramiz. Hozir bitta qoida yetarli: bog'liq qadamlar tartibi — zanjir bo'yicha, mustaqil zanjirlar orasidagi tartibga esa tayanmang.

Tekshirib ko'ring: Yuqoridagi kodda choy zanjiriga uchinchi .then(() => console.log("5. choy ichildi")) qo'shsak, u qayerda chiqadi?

Javob

Eng oxirida — "4. choy quyildi" dan keyin. U «4» ning Promise'ini kutadi va navbatga faqat «4» tugagach tushadi. Shu paytda osh zanjirida navbatga qo'yiladigan hech narsa qolmagan.

5. Xato zanjir bo'ylab oqadi

5.1 Rad etilgan Promise then larni o'tkazib yuboradi

Zanjirning biror qadami rad etilsa, keyingi hamma then lar o'tkazib yuboriladi — xato eng yaqin catch ga yetguncha pastga tushadi:

js
function stolTop(odam) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (odam > 20) reject(new Error("Bunday katta stol yo'q"));
      else resolve(5);
    }, 100);
  });
}

stolTop(25)
  .then((stol) => {
    console.log("bron qilinmoqda:", stol);
    return `${stol}-stol`;
  })
  .then((bron) => console.log("buyurtma:", bron))
  .catch((xato) => console.log("Xato:", xato.message))
  .finally(() => console.log("Kassir bo'shadi"));

Konsolda:

text
Xato: Bunday katta stol yo'q
Kassir bo'shadi

Ikkala then dan hech narsa chiqmadi. Xato try/catch dagi throw kabi ishlaydi: throw dan keyingi qatorlar bajarilmaydi, boshqaruv to'g'ri catch ga o'tadi (try / catch / finally). Zanjir — asinxron try bloki, catch — uning catch qismi.

5.2 then ichidagi throw

Jadvaldagi oxirgi qator: then callback'i throw qilsa, uning Promise'i rad etiladi. Bu zanjirda o'z xatolaringizni chiqarishning yo'li:

js
const narx = new Promise((resolve) => resolve(35000));

narx
  .then((n) => {
    if (n > 30000) {
      throw new Error(`Hamyonda yetmaydi: ${n} so'm`);
    }
    return n;
  })
  .then((n) => console.log("To'landi:", n))
  .catch((xato) => console.log("Xato:", xato.message));

Konsolda:

text
Xato: Hamyonda yetmaydi: 35000 so'm

then ichidagi throw dastur to'xtatmadi — u keyingi Promise'ning rad etilishiga aylandi. Buni Callbacklar darsidagi taymer ichidagi throw bilan solishtiring: u yerda dastur yiqilgan edi. Promise callback'larni o'zining "himoya qobig'i" ichida chaqiradi va har xatoni ushlab, rad etishga aylantiradi.

Kutilmagan xatolar ham shunday ushlanadi: undefined.length kabi TypeError ham zanjir oxiridagi catch ga keladi.

Tekshirib ko'ring: Yuqoridagi kodda narx 25000 bo'lsa, nima chiqadi? throw qatori bajariladimi?

Javob

To'landi: 25000. Shart (25000 > 30000) bajarilmaydi, throw ishlamaydi. Birinchi then 25000 ni qaytaradi, ikkinchi then uni oladi, catch esa jim o'tkazib yuboriladi.

5.3 catch dan keyin zanjir davom etadi

catch ham then kabi yangi Promise qaytaradi. Agar catch callback'i oddiy qiymat qaytarsa — zanjir tiklanadi: keyingi then lar yana ishlaydi.

js
function narxniSora(taom) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (taom === "osh") resolve(35000);
      else reject(new Error(`Server javob bermadi: ${taom}`));
    }, 100);
  });
}

const ESKI_NARX = 28000;

narxniSora("lag'mon")
  .catch((xato) => {
    console.log("Ogohlantirish:", xato.message);
    return ESKI_NARX;
  })
  .then((narx) => console.log(`Lag'mon: ${narx} so'm`));

Konsolda:

text
Ogohlantirish: Server javob bermadi: lag'mon
Lag'mon: 28000 so'm

Server javob bermadi, catch eski narxni qaytardi va zanjir "muvaffaqiyatli" davom etdi. Bu — xatodan tiklanish (recovery): zaxira qiymat, keshdagi ma'lumot, standart sozlama.

Lekin bu qobiliyat tuzoq ham bo'lishi mumkin. catch faqat xatoni chiqarib, hech narsa qaytarmasa — keyingi then undefined bilan ishlaydi: Lag'mon: undefined so'm. catch ni qayerga qo'yishni o'ylab tanlang:

  • Oxirida — "biror narsa xato bo'lsa, butun ish bekor". Eng ko'p uchraydigan holat.
  • O'rtada — "shu qadam xato bersa, zaxira bilan davom et". Faqat zaxira qiymat qaytarganda.

Xatoni ushlab, lekin uni o'zingiz hal qila olmasangiz — qayta throw qiling. Shunda u keyingi catch ga boradi.

5.4 then(ok, xato) va then(ok).catch(xato) farqi

Promise asoslari da then ning ikkinchi argumenti haqida "nozik farq bor" degan edik. Mana u:

js
const narx = new Promise((resolve) => resolve(35000));

narx
  .then(
    (n) => { throw new Error("1-zanjirda xato"); },
    (xato) => console.log("ikkinchi argument:", xato.message),
  )
  .catch((xato) => console.log("tashqi catch:", xato.message));

narx
  .then((n) => { throw new Error("2-zanjirda xato"); })
  .catch((xato) => console.log("catch:", xato.message));

Konsolda:

text
tashqi catch: 1-zanjirda xato
catch: 2-zanjirda xato

Birinchi zanjirda "ikkinchi argument" umuman chiqmadi. U faqat oldingi Promise'ning — narx ning — xatosini ko'radi. O'zi bilan yonma-yon turgan birinchi argumentdagi xato esa undan "o'tib ketdi" va pastdagi catch ga tushdi. Agar u yerda catch bo'lmaganda — xato ushlanmay qolardi.

catch esa zanjirda keyin turadi va undan oldingi hamma narsani, jumladan then ichidagi xatoni ham ushlaydi. Shuning uchun qoida: then(ok, xato) o'rniga then(ok).catch(xato) yozing.

Tekshirib ko'ring: p.catch(() => "zaxira").then((x) => console.log(x)) — p bajarilgan bo'lsa ("osh" bilan) nima chiqadi? Rad etilgan bo'lsa-chi?

Javob

Bajarilgan bo'lsa — osh: catch bajarilgan Promise'ni o'zgartirmasdan o'tkazib yuboradi. Rad etilgan bo'lsa — zaxira: catch xatoni ushlab, zaxira qiymat qaytardi va zanjir tiklandi.

6. finally zanjirda

finally zanjirning istalgan joyida turishi mumkin va u natijani o'zgartirmaydi — qiymatni ham, xatoni ham o'zidan o'tkazib yuboradi:

js
const narx = new Promise((resolve) => resolve(35000));

narx
  .finally(() => console.log("Yuklanish belgisi yashirildi"))
  .then((n) => console.log("Narx:", n));

Konsolda:

text
Yuklanish belgisi yashirildi
Narx: 35000

finally callback'i hech narsa olmadi, lekin 35000 uning ustidan o'tib, keyingi then ga yetdi. finally ichida return qilsangiz ham, u e'tiborsiz qoladi. Bitta istisno bor: finally ichida throw qilinsa (yoki u rad etiladigan Promise qaytarsa), zanjir shu yangi xato bilan rad etiladi. Shuning uchun finally dagi kod sodda va xavfsiz bo'lsin — try/finally dagi kabi.

Tekshirib ko'ring: Zanjir .catch(...) bilan emas, .finally(...) bilan tugadi, oldingi qadam esa rad etildi. Xato ushlandimi?

Javob

Yo'q. finally xatoni o'zidan o'tkazib yuboradi — uning callback'i ishlaydi, lekin zanjir hamon rad etilgan holda qoladi. Xato ushlanmagan rad etilish bo'ladi. Ushlash uchun catch kerak: odatda .catch(...).finally(...) tartibida.

7. Ko'p uchraydigan xatolar

7.1 return ni unutish

Promise zanjiridagi eng ko'p uchraydigan xato. Ikki ko'rinishi bor.

Birinchisi — qiymat yo'qoladi:

js
const narx = new Promise((resolve) => resolve(35000));

narx
  .then((n) => {
    n * 2;
  })
  .then((jami) => console.log("Jami:", jami));

Konsolda:

text
Jami: undefined

Jingalak qavsli arrow funksiyada return yozilmasa — funksiya undefined qaytaradi (Function expression va arrow funksiyalar). Qavssiz shakl (n) => n * 2 esa qiymatni o'zi qaytaradi.

Ikkinchisi — xavfliroq: zanjir kutmaydi.

js
function kut(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

kut(100)
  .then(() => {
    kut(300).then(() => console.log("Choy damlandi"));
  })
  .then(() => console.log("Choy quyildi"));

Konsolda:

text
Choy quyildi
Choy damlandi

Choy damlanmasdan quyildi! Birinchi then kut(300) ni qaytarmadi — u undefined qaytardi. Zanjir kutadigan hech narsa ko'rmadi va darhol keyingi qadamga o'tdi. Qadamma-qadam kuzating:

Damlash zanjirdan tashqarida, "o'z-o'zicha" davom etdi. Uning xatosi ham hech qanday catch ga yetmaydi. Tuzatish: then ichida boshlangan har asinxron ishni return qiling: return kut(300).then(...). Shunda uchinchi qadamda 1-then'ning Promise'i 300 ms kutadi va "Choy quyildi" "Choy damlandi" dan keyin chiqadi.

7.2 Ichma-ich then (Promise piramidasi)

js
stolTop(4).then((stol) => {
  return bronQil(stol).then((bron) => {
    return chekYoz(bron).then((chek) => {
      console.log(chek);
    });
  });
});

Bu kod ishlaydi — return lar joyida. Lekin bu o'sha callback hell: Promise faqat nomigagina ishlatilgan. Tuzatish: har then dan Promise'ni qaytaring va keyingi then ni tashqarida, zanjirga ulang — «Callback hell → tekis zanjir» bo'limidagi kabi.

Odamlar ichma-ich yozishning bitta "sababi" bor: pastki qadamga yuqoridagi natija kerak. Masalan, chekda ham stol raqami, ham buyurtma bo'lsin. Tekis zanjirda stol faqat birinchi then da ko'rinadi. Ikki yechim:

  • Natijalarni obyektga yig'ib uzatish: .then((stol) => bronQil(stol).then((bron) => ({ stol, bron }))) — bitta qisqa ichki then, piramida emas.
  • Keyingi darsdagi async/await — u yerda hamma natija oddiy o'zgaruvchida turadi va bu muammo umuman yo'q.

7.3 Zanjir oxirida catch yo'q

catch siz zanjirdagi har qanday xato — ushlanmagan rad etilish: Node'da dastur to'xtaydi, brauzerda Uncaught (in promise) (Promise asoslari). Qoida: zanjir yoki qaytariladi (uni chaqirgan kod catch qiladi), yoki catch bilan tugaydi. Uchinchi yo'l yo'q.

7.4 catch dan keyin "muvaffaqiyat"

catch xatoni chiqardi-yu, hech narsa qaytarmadi — keyingi then lar undefined bilan ishlaydi va "Bron tasdiqlandi!" deb yozib yuboradi. Tuzatish: zanjirni to'xtatish kerak bo'lsa, catch ni oxiriga qo'ying yoki uning ichida xatoni qayta throw qiling.

7.5 Tayyor Promise'ni new Promise ga o'rash

Zanjirni o'rgangan odam ba'zan shunday yozadi:

js
function stolTop(odam) {
  return new Promise((resolve, reject) => {
    kut(100)
      .then(() => resolve(5))
      .catch((xato) => reject(xato));
  });
}

kut(100) allaqachon Promise qaytaradi — uni yana new Promise ga o'rash ortiqcha. Bu kod uzunroq va xavfliroq: then ichida biror narsa xato bersa-yu, reject ga ulanishni unutsangiz, tashqi Promise abadiy kutib qoladi. Ingliz tilida bu "explicit construction anti-pattern" ("ortiqcha konstruktor") deyiladi. Anti-pattern — ko'p uchraydigan, lekin yomon yechim. Tuzatish: mavjud zanjirni qaytaring — return kut(100).then(() => 5);. new Promise faqat callback'li yoki taymerli kodni birinchi marta Promise'ga aylantirish uchun kerak.

8. Mashqlar

1-mashq (oson): Yo'qolgan qiymat

Sardorning kodi Chegirmali narx: undefined so'm chiqaryapti. Xatoni toping va tuzating — Chegirmali narx: 31500 so'm chiqsin.

js
const narx = new Promise((resolve) => resolve(35000));

narx
  .then((n) => {
    const chegirma = n * 0.1;
    n - chegirma;
  })
  .then((yangi) => console.log(`Chegirmali narx: ${yangi} so'm`));
Yechim
js
const narx = new Promise((resolve) => resolve(35000));

narx
  .then((n) => {
    const chegirma = n * 0.1;
    return n - chegirma;
  })
  .then((yangi) => console.log(`Chegirmali narx: ${yangi} so'm`));

Konsolda:

text
Chegirmali narx: 31500 so'm

Birinchi then hisobladi, lekin natijani qaytarmadi. Jingalak qavsli funksiyada return shart. Keyingi then aynan shu return qiymatini oladi.

2-mashq (o'rta): Choy zanjiri

Callbacklar darsidagi uch qadamli ko'k choyni Promise bilan qayta yozing. suvQaynat(), choySol(suv), damla(choynak) — har biri 100 ms dan keyin bajariladigan Promise qaytarsin (natijalar: "qaynoq suv", "<suv> + ko'k choy", "<choynak> (damlandi)"). Ularni tekis zanjirga ulang, oxirida catch bo'lsin. Natija: Tayyor: qaynoq suv + ko'k choy (damlandi).

Yechim
js
function suvQaynat() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("qaynoq suv"), 100);
  });
}
function choySol(suv) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`${suv} + ko'k choy`), 100);
  });
}
function damla(choynak) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`${choynak} (damlandi)`), 100);
  });
}

suvQaynat()
  .then(choySol)
  .then(damla)
  .then((choy) => console.log("Tayyor:", choy))
  .catch((xato) => console.log("Xato:", xato.message));

Konsolda:

text
Tayyor: qaynoq suv + ko'k choy (damlandi)

Callback'li yechimda uchta nomli funksiya (suvQaynadi, choySolindi, damlandi) va har birida if (xato) kerak edi. Bu yerda zanjir o'zi "keyingi qadam" ni biladi, xato esa bitta catch da.

3-mashq (qiyin): Qayta urinish

Oshxona serveri ba'zan javob bermaydi. kurs/mashqlar/11/06-zanjir/qaytaUrin.mjs faylida qaytaUrin(ish, marta) funksiyasini yozing: ish — Promise qaytaradigan funksiya. qaytaUrin uni chaqirsin; rad etilsa — marta tugaguncha qayta chaqirsin; urinishlar tugasa — oxirgi xato bilan rad etilsin. Ishora: ish().catch(...) ichida qaytaUrin ni o'zini qaytaring — catch Promise qaytarsa, zanjir uni kutadi.

Sinov uchun ishonchsizServer tayyor: u birinchi ikki chaqiruvda rad etadi, uchinchisida bajariladi.

js
let chaqiruv = 0;
function ishonchsizServer() {
  chaqiruv++;
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (chaqiruv < 3) reject(new Error(`${chaqiruv}-urinish xato`));
      else resolve(`${chaqiruv}-urinishda narx: 28000`);
    }, 100);
  });
}
Yechim
js
let chaqiruv = 0;
function ishonchsizServer() {
  chaqiruv++;
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (chaqiruv < 3) reject(new Error(`${chaqiruv}-urinish xato`));
      else resolve(`${chaqiruv}-urinishda narx: 28000`);
    }, 100);
  });
}

function qaytaUrin(ish, marta) {
  return ish().catch((xato) => {
    console.log("Xato:", xato.message);
    if (marta <= 1) {
      throw xato;
    }
    return qaytaUrin(ish, marta - 1);
  });
}

qaytaUrin(ishonchsizServer, 5)
  .then((natija) => console.log(natija))
  .catch((xato) => console.log("Taslim:", xato.message));

Konsolda:

text
Xato: 1-urinish xato
Xato: 2-urinish xato
3-urinishda narx: 28000

catch ichida ikki yo'l bor. Urinishlar tugagan bo'lsa — throw xato: zanjir rad etiladi va tashqaridagi catch "Taslim" deb yozadi. Aks holda — return qaytaUrin(...): yangi urinishning Promise'i qaytariladi, zanjir uni kutadi. marta ni 2 qilib sinang — Taslim: 2-urinish xato chiqadi.

Bu rekursiya, lekin stek o'smaydi: har yangi urinish taymer va microtask'dan, bo'sh stekdan boshlanadi. Real ilovalarda urinishlar orasiga o'sib boradigan kutish qo'yiladi — eksponensial kutish (Taymerlar darsida ko'rgansiz). Qayta urinish, vaqt chegarasi va boshqa tayyor "retseptlar" — Asinxron naqshlar darsida.

bash
git add 11/06-zanjir/qaytaUrin.mjs
git commit -m "11/06: qaytaUrin — Promise bilan qayta urinish"

9. Real ishda

  • Eski va kutubxona kodi. async/await dan oldin (2017 gacha) hamma asinxron kod then zanjirlari bilan yozilgan. Ko'p kutubxonalarning hujjatlarida hamon .then().catch() misollari turibdi.
  • Kichik qayta ishlash. Bitta qatorli o'zgartirish uchun zanjir qulay: narxniSora("osh").then((n) => n * 2). Tarmoq so'rovida ham shunday shakl ko'p uchraydi: javobni olib, uni JSON'ga aylantirish — fetch asoslari da ko'rasiz.
  • Kod sharhi. Tajribali dasturchi zanjirli kodda birinchi navbatda ikki narsani qidiradi: har then ichida boshlangan asinxron ish return qilinganmi va zanjir catch bilan tugaydimi (yoki qaytariladimi). Bu ikki xato loglarda "ba'zan" chiqadigan, qayta takrorlash qiyin muammolarning eng ko'p uchraydigan sababi.
  • Xatodan tiklanish. Zaxira qiymat (catch(() => keshdagiNarx)), qayta urinish (3-mashq) — real ilovalarda har kuni.
  • Intervyu. "then nima qaytaradi?", "then ichida return unutilsa nima bo'ladi?", "then(a, b) va then(a).catch(b) farqi?", ikki zanjirning chiqish tartibi — klassik savollar.

Xulosa

  • then (va catch, finally) doim yangi Promise qaytaradi — shuning uchun ularni zanjirga ulash mumkin.
  • Callback qiymat qaytarsa — keyingi then uni oladi; Promise qaytarsa — zanjir uni kutadi; throw qilsa — zanjir rad etiladi.
  • Callback hell → tekis zanjir: har qadam keyingi Promise'ni qaytaradi, oxirida bitta catch.
  • Rad etish keyingi then larni o'tkazib, eng yaqin catch ga tushadi; catch qiymat qaytarsa — zanjir tiklanadi.
  • Har then — alohida microtask; mustaqil zanjirlar navbatma-navbat qadam tashlaydi.
  • Eng ko'p xato — then ichida return ni unutish: qiymat yo'qoladi va zanjir kutmaydi.

Keyingi dars: async / await — zanjirni sinxron kod kabi yozish: const stol = await stolTop(4);, xatolar esa tanish try/catch bilan.

Manbalar

  • MDN: "Using promises" (Chaining, Error handling), "Promise.prototype.then()" — developer.mozilla.org
  • ECMAScript spetsifikatsiyasi: "Promise.prototype.then", "PromiseResolveThenableJob" — tc39.es/ecma262
  • Jake Archibald, "JavaScript Promises: an introduction" (Chaining, Error handling) — web.dev
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript Promise zanjiri: then qaytaradigan yangi Promise — IlmHamroh