Promise chaining
Promise chaining'ning to'g'ri yozilishi: har doim return qilish qoidasi, ichma-ich .then() yozishning xatoligi, zanjir ichida qiymat transformatsiyasi, xatoning zanjir bo'ylab qanday oqishi, .finally(), va V8'da har bir .then() bo'g'ini qanday alohida Promise ekani.
52-dars
Promise chaining
Bu mavzuni oldingi Promise ichki mexanizmida ko'rgan narsalarga tayanib, endi amaliy tomondan — qanday to'g'ri yozish, qanday xatolarga yo'l qo'yilmasligi kerak nuqtai nazaridan ochamiz.
1. Oddiy ta'rif
Promise chaining — bir nechta .then()ni ketma-ket ulash, har biri oldingisining natijasi ustida ishlaydi:
fetchUser(1)
.then((user) => fetchOrders(user.id))
.then((orders) => fetchOrderDetails(orders[0].id))
.then((details) => console.log(details));
Bu — Callback Hell'ning "tekislangan" (flat) versiyasi: ichma-ich emas, yon-baxsh, o'qish oson.
2. Asosiy qoida — HAR DOIM return qiling
Bu — Promise chaining'dagi eng ko'p uchraydigan xato manbai. Oldingi mavzuda (Promise ichki mexanizmi) ko'rganimiz Resolution Procedure'ni eslang: .then() ichidagi funksiya nima qaytarsa, keyingi .then() aynan o'shani oladi.
XATO — return unutilgan:
fetchUser(1)
.then((user) => {
fetchOrders(user.id); // ← return YO'Q!
})
.then((orders) => {
console.log(orders); // undefined — chunki oldingi .then() hech narsa qaytarmadi!
});
Bu yerda fetchOrders(user.id) — chaqiriladi, ish boshlanadi, lekin uning natijasi (Promise) hech kimga uzatilmaydi. Keyingi .then() — bu Promise haqida bilmaydi ham, u avvalgi .then()ning undefined qaytarganini ko'radi va darhol, fetchOrders hali tugamagan holda, ishga tushadi.
TO'G'RI — return bilan:
fetchUser(1)
.then((user) => {
return fetchOrders(user.id); // ← Promise QAYTARILDI
})
.then((orders) => {
console.log(orders); // to'g'ri natija, fetchOrders TUGAGANDAN keyin
});
Yoki qisqa arrow function bilan ({} yozilmasa, avtomatik return):
fetchUser(1)
.then((user) => fetchOrders(user.id)) // bitta ifoda — avtomatik return
.then((orders) => console.log(orders));
Diqqat: {} bilan yozsangiz, avtomatik return ishlamaydi — bu ko'p dasturchini "tutadigan" joy:
.then((user) => {
fetchOrders(user.id); // { } ichida — return KERAK, avtomatik emas!
})
3. Nega chaining'da "flatten" bo'ladi — V8 mexanizmini eslatib o'tish
Oldingi mavzuda ko'rgan Promise Resolution Procedure aynan shu yerda ishga tushadi: .then() ichida Promise qaytarilsa, tashqi (.then() yaratgan) Promise o'sha ichki Promise hal bo'lguncha pendingda qoladi, keyin uning natijasini "meros qiladi":
.then((user) => fetchOrders(user.id))
│
▼ fetchOrders() — YANGI Promise qaytaradi (masalan, p_inner)
│
▼ tashqi .then() yaratgan Promise (p_outer) — HALI pending
│ V8: p_inner'ga ICHKI .then() ulaydi
│
▼ p_inner hal bo'lganda (masalan, orders massivi bilan)
│
▼ p_outer HAM aynan shu natija bilan fulfilled bo'ladi
│
▼ ZANJIRDAGI KEYINGI .then() — endi ishga tushadi, orders'ni oladi
Shu tufayli, nechta bosqich bo'lishidan qat'i nazar, .then().then().then()... — hech qachon Promise<Promise<Promise>>> bo'lib "chalkashib" qolmaydi.
4. Xato — ichma-ich .then() yozish (Chaining'ning noto'g'ri, "callback-uslubi" varianti)
// XATO — bu Callback Hell'ning Promise bilan qaytarilgan shakli!
fetchUser(1).then((user) => {
fetchOrders(user.id).then((orders) => {
fetchOrderDetails(orders[0].id).then((details) => {
console.log(details); // 3 daraja chuqurlik — nega Promise ishlatdik?!
});
});
});
Bu texnik jihatdan ishlaydi, lekin Promise chaining'ning maqsadini yo'qqa chiqaradi. To'g'ri yo'l — har doim tekis (flat) zanjir, return orqali:
fetchUser(1)
.then((user) => fetchOrders(user.id))
.then((orders) => fetchOrderDetails(orders[0].id))
.then((details) => console.log(details));
5. Zanjir ichida qiymatni "transformatsiya qilish"
.then() — Promise qaytarish shart emas, oddiy qiymat ham qaytarishi mumkin, u avtomatik keyingi bosqichga "sinxron ravishda" (lekin baribir microtask sifatida) uzatiladi:
fetchUser(1)
.then((user) => user.id) // oddiy qiymat qaytarilyapti
.then((id) => id * 2) // oldingisini qayta ishlaydi
.then((doubled) => console.log(doubled));
Bu ikkalasi — Promise qaytarish (fetchOrders(...)) va oddiy qiymat qaytarish (user.id) — bir xil .then() interfeysi orqali ishlaydi, chunki Resolution Procedure ikkalasini ham qo'llab-quvvatlaydi (oldingi mavzuda ko'rgan "1-holat: oddiy qiymat, 2-holat: Promise" farqi).
6. Xato zanjir bo'ylab qanday "oqadi" — amaliy misol
fetchUser(1)
.then((user) => {
if (!user) throw new Error('foydalanuvchi topilmadi');
return fetchOrders(user.id);
})
.then((orders) => {
console.log(orders.length); // agar oldingi throw bo'lsa — BU O'TKAZIB YUBORILADI
return orders;
})
.then((orders) => {
console.log("yana o'tkaziladi"); // bu ham o'tkaziladi
})
.catch((err) => {
console.log('faqat shu yerda ushlanadi:', err.message);
});
Bu — oldingi mavzuda (Error handling) ko'rgan mexanizm: xato tashlangan zahoti, zanjirdagi barcha keyingi .then()lar (agar ularda ikkinchi argument — onRejected — bo'lmasa) avtomatik o'tkazib yuboriladi, faqat birinchi .catch() (yoki ikkinchi argumentli .then()) uni ushlaydi.
Muhim: .catch()dan keyin yana .then() qo'shsangiz, zanjir davom etadi — chunki .catch() ham yangi Promise qaytaradi, va agar xato ichida qayta throw qilinmasa, u fulfilled hisoblanadi:
fetchUser(1)
.then((user) => { throw new Error('xato'); })
.catch((err) => {
console.log('ushlandi:', err.message);
return 'zaxira qiymat'; // xatodan keyin ZANJIR DAVOM ETADI
})
.then((val) => console.log(val)); // "zaxira qiymat" — chunki catch xatoni "yopdi"
7. .finally() — natija/xatodan qat'i nazar bajariladigan qism
fetchData()
.then((data) => console.log(data))
.catch((err) => console.log(err))
.finally(() => console.log('yuklanish tugadi')); // har doim ishga tushadi
.finally() — argumentga (natija yoki xato) kirish huquqiga ega emas, faqat "har qanday holatda ham shu ishni bajar" (masalan, loading spinner'ni yopish) uchun ishlatiladi. Ichkarida u — .then(onFinally, onFinally)ga o'xshab ishlaydi, lekin natijani/xatoni o'zgartirmasdan, zanjir bo'ylab shaffof o'tkazib yuboradi.
8. V8 darajasida — zanjirning har bir bo'g'ini nima
Har bir .then() chaqiruvi — Promise ichki mexanizmi mavzusida ko'rgan [[PromiseFulfillReactions]] / [[PromiseRejectReactions]] ro'yxatiga bitta yozuv qo'shadi, va yangi Promise obyekti yaratadi. Zanjir vizual jihatdan chiziq bo'lib ko'rinsa ham, xotirada bu — bir-biriga bog'langan, alohida-alohida Promise obyektlari zanjiri:
p0 (fetchUser natija Promise)
│ .then() chaqirilganda:
│ - p0.reactions.push(reaction1)
│ - p1 = yangi Promise yaratiladi, QAYTARILADI
▼
p1 ──.then()──► p2 ──.then()──► p3 ──.catch()──► p4
Har bir bosqich — o'z navbatida, oldingisi fulfilled/rejected bo'lgach, alohida microtask sifatida ishga tushadi. Shuning uchun uzun zanjir (masalan, 5 ta .then()) — 5 ta ketma-ket microtask tickni anglatadi, hammasi bitta Macrotask ichida, lekin har biri alohida "sakrash" bilan (Event Loop mavzusida ko'rgan mexanizm asosida).
Xulosa — Chaining'ning uchta oltin qoidasi
- Har doim
returnqiling — aks holda keyingi bosqich sizning Promise natijangizni kutmaydi - Ichma-ich yozmang — har bir
.then()avvalgisidan Promise yoki qiymat qaytarsin, zanjir tekis bo'lsin - Bitta
.catch()— zanjir oxirida yetarli — u barcha oldingi bosqichlardagi xatolarni ushlaydi, har bir bosqichda alohidatry/catchshart emas