Mundarija (37)
- Bu darsda
- 1. Nega bu kerak?
- 2. yield* — ishni topshirish
- 2.1 Muammo: bo'limlarni qo'lda ulash
- 2.2 Yechim: yield*
- 2.3 yield* istalgan iterable bilan ishlaydi
- 2.4 yield* ning natijasi
- 2.5 Rekursiya: ichma-ich bo'limlar
- 3. next(qiymat) — generatorga ma'lumot yuborish
- 3.1 yield — ikki tomonlama eshik
- 3.2 Kassa hisobchisi
- 3.3 Qadamma-qadam
- 3.4 Birinchi next() argumenti yo'qoladi
- 3.5 Korutin g'oyasi
- 4. return(qiymat) — tashqaridan yakunlash
- 5. throw(xato) — generator ichiga xato otish
- 5.1 Xato to'xtagan yield da paydo bo'ladi
- 5.2 Generator xatoni ushlasa
- 5.3 Boshlanmagan generatorga throw()
- 6. Amaliyotda: generator va "ijrochi"
- 6.1 Generator nima qilishni aytadi, ijrochi bajaradi
- 6.2 Nega bu foydali: sinash oson
- 6.3 Redux-saga va async/await ning ildizi
- 7. Ko'p uchraydigan xatolar
- 7.1 Birinchi next() ga ma'lumot berish
- 7.2 yield va yield* ni chalkashtirish
- 7.3 yield* ga iterable bo'lmagan qiymat
- 7.4 Ushlanmagan throw()
- 7.5 Tugagan generatordan javob kutish
- 8. Mashqlar
- 1-mashq (oson): Kunlik menyu
- 2-mashq (o'rta): Ichma-ich massivni tekislash
- 3-mashq (qiyin): Baholarning o'rtachasi
- 4-mashq: Vazifalar qadami — iterator bitta qatorga, branch'ni yakunlash
- 9. Real ishda
- Xulosa
- Manbalar
JavaScript generatorlar chuqur: yield*, next(qiymat), return() va throw()
Qisqacha:
yield*ishni boshqa generatorga yoki istalgan iterable'ga topshiradi: uning hamma qiymatlarini birma-bir tashqariga beradi. Aloqani esanext(qiymat)ikki tomonlama qiladi: berilgan qiymat to'xtab turganyieldifodasining natijasiga aylanadi. Metodlardanreturn(x)generatorni tashqaridan yakunlaydi,throw(xato)esa to'xtaganyieldjoyida xato "otadi". Shu to'rt vosita generatorni korutinga — chaqiruvchi bilan navbatma-navbat gaplashadigan funksiyaga aylantiradi.
Bu darsda
yield*bilan bir nechta generatorni bittaga birlashtirasiz va ichma-ich tuzilmani rekursiv aylanasiz.next(qiymat)bilan generatorga ma'lumot yuborasiz va birinchinext()argumenti nega yo'qolishini tushuntira olasiz.return()vathrow()generatorga qanday ta'sir qilishini bilasiz.- Korutin g'oyasini va redux-saga kabi kutubxonalar generatorni qanday ishlatishini tushunasiz.
vazifalardagi qo'lda yozilgan iteratorni bittayield*qatoriga qisqartirasiz.
Oldin bilishingiz kerak: Generator funksiyalar, O'z iterable obyektingizni yasash, Rekursiya asoslari, Stack trace va xato turlari.
1. Nega bu kerak?
O'tgan darsda generator bir tomonga gapirdi: qiymat berdi, to'xtadi, yana berdi. Chaqiruvchi faqat "keyingisi" deb so'radi. Real ishda esa ikki savol tez chiqadi.
Birinchi savol: bo'laklardan yig'ish. «Bahor» menyusi bo'limlardan iborat: issiq taomlar, ichimliklar. Har bo'lim — alohida generator. Butun menyu — ularning ketma-ket birlashmasi. Buni qanday qisqa yozamiz?
Ikkinchi savol: javob qaytarish. Kassa generatori buyurtmalar summasini yuritsin deylik: biz unga har taomning narxini beramiz, u esa yangi jamini qaytaradi. Hozircha generator faqat beradi — undan olish yo'lini bilmaymiz.
Hayotdan o'xshatish: ofitsiant va oshpaz. Ofitsiant oshxona eshigidan "bitta osh!" deydi va kutadi. Oshpaz oshni tayyorlab, likopchani eshikka qo'yadi va keyingi buyurtmani kutadi. Ikki tomon navbatma-navbat ishlaydi, har biri bir-biriga nimadir uzatadi. Generator va chaqiruvchi o'rtasida ham xuddi shunday "eshik" bor — yield. Bugun u eshikdan ikki tomonga ham narsa o'tishini ko'ramiz.
2. yield* — ishni topshirish
2.1 Muammo: bo'limlarni qo'lda ulash
function* issiqTaomlar() {
yield "osh";
yield "lag'mon";
}
function* ichimliklar() {
yield "ko'k choy";
}
function* menyu() {
for (const taom of issiqTaomlar()) {
yield taom;
}
for (const taom of ichimliklar()) {
yield taom;
}
}
console.log([...menyu()]); // [ 'osh', "lag'mon", "ko'k choy" ]Ishlaydi, lekin har bo'lim uchun uch qatorli bir xil sikl: "ichkaridagidan ol — tashqariga ber". Bo'limlar o'nta bo'lsa — o'ttiz qator takror.
2.2 Yechim: yield*
function* issiqTaomlar() {
yield "osh";
yield "lag'mon";
}
function* ichimliklar() {
yield "ko'k choy";
}
function* menyu() {
yield* issiqTaomlar();
yield* ichimliklar();
}
console.log([...menyu()]); // [ 'osh', "lag'mon", "ko'k choy" ]yield* (yulduzchali yield) — "bu iterable'ning hamma qiymatini birma-bir men nomimdan ber" degani. Inglizcha bu delegatsiya (delegation) — ishni boshqaga topshirish. menyu generatori yield* issiqTaomlar() ga yetganda, issiqTaomlar tugaguncha tashqaridagi har next() to'g'ridan-to'g'ri unga boradi. U tugagach — menyu keyingi qatordan davom etadi.
O'xshatish: brigadir. Buyurtmachi faqat brigadir bilan gaplashadi. Brigadir esa "devorni Ali quradi" deb ishni topshiradi. Ali tugatguncha buyurtmachi har g'ishtni Alidan oladi, lekin brigadir orqali. Ali tugadi — brigadir keyingi ishchiga o'tadi.
2.3 yield* istalgan iterable bilan ishlaydi
yield* faqat generatorni emas, har qanday iterable'ni qabul qiladi — massiv, satr, Set, Map, o'z iterable'ingiz:
function* namuna() {
yield ["osh", "manti"];
yield* ["osh", "manti"];
yield* "ok";
}
console.log([...namuna()]);Konsolda:
[ [ 'osh', 'manti' ], 'osh', 'manti', 'o', 'k' ]Farqga qarang:
| Yozuv | Nima beradi |
|---|---|
yield massiv |
massivning o'zini, bitta qiymat sifatida |
yield* massiv |
massivning elementlarini, bittadan |
yield* "ok" |
harflarni bittadan — satr ham iterable |
yield* ichida aslida o'tgan darslardagi protokol ishlaydi: avval [Symbol.iterator](), keyin next() lar done: true gacha.
2.4 yield* ning natijasi
yield* ham ifoda: u ichki generatorning return qiymatiga teng. O'tgan darsda "return qiymatini for...of tashlab yuboradi" degan edik. yield* esa uni ushlaydi:
function* issiqTaomlar() {
yield "osh";
yield "lag'mon";
return 2;
}
function* ichimliklar() {
yield "ko'k choy";
return 1;
}
function* menyu() {
const a = yield* issiqTaomlar();
const b = yield* ichimliklar();
console.log(`Jami taomlar: ${a + b}`);
}
console.log([...menyu()]);Konsolda:
Jami taomlar: 3
[ 'osh', "lag'mon", "ko'k choy" ]Ichki generatorlar qiymatlarni yield bilan tashqariga berdi, return bilan esa faqat o'z brigadiriga hisobot berdi. Tashqi iste'molchi (spread) bu sonlarni ko'rmadi — ular ichkarida qoldi. Bu ikki xil kanal: yield — mijozga, return — boshliqqa.
2.5 Rekursiya: ichma-ich bo'limlar
yield* ning eng kuchli joyi — rekursiya bilan. Menyu bo'limlari ichida yana bo'limlar bo'lsin:
flowchart TD
M["Menyu"] --> I["Issiq<br/>Osh, Manti"]
M --> C["Ichimlik<br/>Ko'k choy"]
C --> S["Sovuq<br/>Kompot"]Hamma taomni qanday chiqaramiz? Har bo'lim uchun bir xil ish: o'z taomlarini ber, keyin har ichki bo'lim uchun xuddi shu ishni qil.
const menyuDaraxti = {
nom: "Menyu",
taomlar: [],
bolimlar: [
{ nom: "Issiq", taomlar: ["Osh", "Manti"], bolimlar: [] },
{
nom: "Ichimlik",
taomlar: ["Ko'k choy"],
bolimlar: [
{ nom: "Sovuq", taomlar: ["Kompot"], bolimlar: [] },
],
},
],
};
function* hammaTaomlar(bolim) {
yield* bolim.taomlar;
for (const ichki of bolim.bolimlar) {
yield* hammaTaomlar(ichki);
}
}
console.log([...hammaTaomlar(menyuDaraxti)]);Konsolda:
[ 'Osh', 'Manti', "Ko'k choy", 'Kompot' ]Ikki qatorli tana: yield* bolim.taomlar — o'z taomlari, yield* hammaTaomlar(ichki) — ichki bo'limga topshirish. Rekursiya asoslari darsidagi to'xtash sharti shu yerda ham bor: bolimlar bo'sh bo'lsa, for aylanmaydi va chuqurga tushish to'xtaydi.
Natija ham dangasa: birinchi ikki taomni olish uchun "Sovuq" bo'limiga tushilmaydi ham. Massiv bilan yozganda esa butun daraxtni avval yig'ish kerak bo'lardi.
Tekshirib ko'ring:
hammaTaomlardagiyield* hammaTaomlar(ichki)niyield hammaTaomlar(ichki)(yulduzchasiz) qilsak,[...hammaTaomlar(menyuDaraxti)]nima beradi?
Javob
[ 'Osh'… ] emas, balki ichki bo'limlar o'rnida generator obyektlari: [ Object [Generator] {}, Object [Generator] {} ] (menyuning o'z taomlari bo'sh). yield ichki generatorni bitta qiymat sifatida berdi — uni ochmadi. Ichma-ich tuzilmani "tekislash" uchun aynan yield* kerak.
3. next(qiymat) — generatorga ma'lumot yuborish
3.1 yield — ikki tomonlama eshik
Hozirgacha yield ni ko'rsatma (statement) sifatida yozdik: yield "osh";. Aslida yield — ifoda, uning natijasi bor. Natija — keyingi next() ga berilgan argument:
const narx = yield jami;Bu qator ikki ishni ikki xil vaqtda qiladi:
yield jami—jamini tashqariga beradi va to'xtaydi.- Keyinroq kimdir
next(35000)ni chaqiradi — generator uyg'onadi,yield jamiifodasi35000ga aylanadi vanarxga yoziladi.
Eshikdan bir tomonga likopcha chiqdi, keyin narigi tomondan buyurtma qog'ozi kirdi.
3.2 Kassa hisobchisi
function* hisobchi() {
let jami = 0;
while (true) {
const narx = yield jami;
jami += narx;
}
}
const kassa = hisobchi();
console.log(kassa.next().value); // 0
console.log(kassa.next(35000).value); // 35000
console.log(kassa.next(5000).value); // 40000Kassa — cheksiz generator (Generator funksiyalar). U har narxni qabul qilib, yangi jamini qaytaradi. jami uning ichida, tashqaridan uni buzib bo'lmaydi. Bu closure'dagi private holatga o'xshaydi, lekin endi u bilan "suhbat" qurish mumkin.
O'zingiz to'ldiring. Yangi hisobchi() generatoriga ketma-ket next(), next(28000), next(30000) chaqirildi. Uchinchi chaqiruvning value si — [:58000]. Undan keyin next(5000) chaqirilsa — [:63000].
3.3 Qadamma-qadam
Keyingi ni bosing. next() ning argumenti qaysi qadamda generator ichiga kirishini kuzating:
3.4 Birinchi next() argumenti yo'qoladi
Vizualning ikkinchi qadamiga qayting: birinchi next() paytida hech qanday yield kutib turmagan. Tana endi boshlanyapti. Shuning uchun birinchi next() ga berilgan argument hech qayerga tushmaydi:
function* savolJavob() {
const javob = yield "Qaysi taom?";
console.log(`Javob: ${javob}`);
}
const suhbat = savolJavob();
console.log(suhbat.next("osh").value); // Qaysi taom?
suhbat.next("manti");Konsolda:
Qaysi taom?
Javob: manti"osh" yo'qoldi — xato ham chiqmadi. Birinchi next() — "ishga tushirish": u generatorni birinchi yield gacha olib boradi. Ma'lumot ikkinchi next() dan boshlab kiradi. Shuning uchun ikki tomonlama generatorlarda birinchi next() odatda argumentsiz chaqiriladi, javobi esa ("Qaysi taom?" kabi) "savol" vazifasini bajaradi.
3.5 Korutin g'oyasi
Kassa misolida ikki kod bo'lagi navbatma-navbat ishladi: chaqiruvchi narx berdi — generator hisobladi va to'xtadi — chaqiruvchi yana narx berdi. Hech biri ikkinchisini to'liq "chaqirib tugatmadi"; ular bir-biriga boshqaruvni uzatib turdi.
sequenceDiagram
participant C as Chaqiruvchi
participant G as hisobchi
C->>G: next()
G-->>C: 0 (yield)
C->>G: next(35000)
Note over G: jami += 35000
G-->>C: 35000 (yield)
C->>G: next(5000)
G-->>C: 40000 (yield)Bunday funksiya korutin (coroutine) deb ataladi — "hamkor funksiya": u o'z ishini yarim yo'lda to'xtatib, boshqaruvni boshqa kodga beradi va keyin to'xtagan joyidan davom etadi. JavaScript generatori — korutinning soddalashgan turi: u boshqaruvni faqat o'zini chaqirgan tomonga qaytara oladi. Bu cheklov amalda ancha qulay: kim kimni uyg'otishi doim aniq.
Tekshirib ko'ring:
const kassa = hisobchi();dan keyin birinchi chaqiruvkassa.next(35000)bo'lsa,valuenima bo'ladi? Keyingikassa.next(5000)chi?
Javob
Birinchisi — 0: argument yo'qoldi, generator faqat birinchi yield jami gacha bordi. Ikkinchisi — 5000: endi narx = 5000, jami = 0 + 5000. 35 000 so'm hisobga kirmadi. Shuning uchun ikki tomonlama generatorni avval argumentsiz next() bilan ishga tushiring.
4. return(qiymat) — tashqaridan yakunlash
Generatorni oxirigacha aylanmasdan to'xtatish kerak bo'lsa — return() metodi:
function* taomlar() {
yield "osh";
yield "manti";
yield "somsa";
}
const g = taomlar();
console.log(g.next()); // { value: 'osh', done: false }
console.log(g.return("bekor")); // { value: 'bekor', done: true }
console.log(g.next()); // { value: undefined, done: true }return("bekor") generatorni to'xtagan yield joyida, xuddi u yerda return "bekor"; yozilgandek, yakunladi. Generator "Tugagan" holatga o'tdi — keyingi next() lar faqat done: true beradi. "manti" va "somsa" hech qachon berilmadi.
O'z iterable obyektingizni yasash darsidagi return() ni eslang — for...of dagi break uni avtomatik chaqiradi. Generatorda bu metod tayyor. yield* orqali topshirilgan bo'lsa, return() ichki generatorga ham yetib boradi: brigadir ishni to'xtatsa, Ali ham to'xtaydi.
Ichki generator yopilayotganda tozalash ishini bajarishi mumkin — buning uchun finally bloki ishlatiladi. Uni try / catch / finally darsida o'rganamiz, hozir bilish shart emas.
Tekshirib ko'ring:
menyu()generatoriyield* issiqTaomlar()ichida, "osh" dan keyin to'xtab turibdi. Tashqaridanmenyugareturn()chaqirilsa, "lag'mon" beriladimi?menyudagi keyingiyield* ichimliklar()chi?
Javob
Ikkalasi ham yo'q. return() avval ichki issiqTaomlar ga yetib boradi va uni yopadi, keyin menyu ning o'zini ham to'xtagan joyida yakunlaydi. yield* dan keyingi qatorlar bajarilmaydi. Brigadir ishni to'xtatdi — na Ali, na keyingi ishchi davom etmaydi.
5. throw(xato) — generator ichiga xato otish
5.1 Xato to'xtagan yield da paydo bo'ladi
Uchinchi metod — throw(). U generatorni uyg'otadi, lekin qiymat bilan emas, xato bilan: to'xtagan yield ifodasi o'rnida xato chiqadi.
Xato uchun Error obyekti kerak — xato xabarini saqlaydigan maxsus obyekt: new Error("matn"). Uni throw va Error obyekti darsida batafsil o'rganamiz; hozircha "xato xabari solingan quti" deb biling.
function* buyurtmalar() {
yield "osh";
yield "manti";
}
const g = buyurtmalar();
g.next();
g.throw(new Error("Buyurtma bekor qilindi"));Konsolda:
Error: Buyurtma bekor qilindiGenerator xatoni ichkarida ushlamadi. Natijada xato g.throw(...) chaqirilgan joyga — tashqariga — qaytib chiqdi va dastur to'xtadi. Node uning ostida stack trace ham chiqaradi (Stack trace va xato turlari). Generator esa endi tugagan: g.next() — { value: undefined, done: true }.
5.2 Generator xatoni ushlasa
Generator ichida xatoni ushlash mumkin. Buning uchun try / catch konstruksiyasi bor: try ichida xato chiqsa, dastur yiqilmaydi — catch bloki ishlaydi. Uni try / catch / finally darsida to'liq o'rganamiz — bu yerda faqat generatorga ta'sirini ko'ramiz. catch (xato) dagi xato — ushlangan Error obyekti, xato.message — uning ichidagi xabar matni:
function* oshxona() {
while (true) {
try {
yield "buyurtma kutilmoqda";
} catch (xato) {
console.log(`Oshxona: ${xato.message}, qayta yoqildi`);
}
}
}
const g = oshxona();
console.log(g.next().value);
console.log(g.throw(new Error("Gaz o'chdi")).value);
console.log(g.next().value);Konsolda:
buyurtma kutilmoqda
Oshxona: Gaz o'chdi, qayta yoqildi
buyurtma kutilmoqda
buyurtma kutilmoqdaXato yield joyida chiqdi, catch uni ushladi va xabarini chiqardi. Sikl davom etdi va keyingi yield ga yetdi — throw() shu qiymatni qaytardi (uchinchi qator). Generator tirik: oxirgi next() yana ishladi (to'rtinchi qator).
5.3 Boshlanmagan generatorga throw()
Generator hali bitta ham next() olmagan bo'lsa, uning ichida try ga kirilmagan — ushlaydigan joy yo'q. throw() xatoni darhol tashqariga qaytaradi va generator tanasiga umuman kirmasdan "Tugagan" holatga o'tadi. Ikki tomonlama generatorlarda birinchi next() ni unutmaslikning yana bir sababi.
Tekshirib ko'ring:
next(),return(x)vathrow(xato)— uchalasi ham generatorni to'xtaganyielddan uyg'otadi. Ular nimasi bilan farq qiladi?
Javob
Farq — to'xtagan yield ifodasi nimaga aylanishida. Agar next(x) bo'lsa — x qiymatiga, va kod davom etadi. Agar return(x) bo'lsa — xuddi o'sha joyda return x; yozilgandek: generator yakunlanadi. Agar throw(xato) bo'lsa — o'sha joyda xato chiqqandek. Ushlansa, kod davom etadi; ushlanmasa, xato tashqariga chiqadi va generator tugaydi.
6. Amaliyotda: generator va "ijrochi"
6.1 Generator nima qilishni aytadi, ijrochi bajaradi
Endi to'rt vositani bitta naqshga yig'amiz. Generator o'zi hech qanday ish qilmasin — faqat topshiriq obyektlarini yield qilsin: "osh narxini top", "chek chiqar". Alohida ijrochi funksiya topshiriqni bajarib, javobni next(javob) bilan qaytarsin:
const narxlar = { osh: 35000, manti: 30000, "ko'k choy": 5000 };
function* buyurtmaJarayoni() {
const osh = yield { tur: "narx", taom: "osh" };
const choy = yield { tur: "narx", taom: "ko'k choy" };
yield { tur: "chek", matn: `Jami: ${osh + choy} so'm` };
}
function ijroEt(generator) {
let qadam = generator.next();
while (!qadam.done) {
const topshiriq = qadam.value;
let javob;
if (topshiriq.tur === "narx") {
javob = narxlar[topshiriq.taom];
} else if (topshiriq.tur === "chek") {
console.log(topshiriq.matn);
}
qadam = generator.next(javob);
}
}
ijroEt(buyurtmaJarayoni());Konsolda:
Jami: 40000 so'mbuyurtmaJarayoni oddiy ketma-ket kod kabi o'qiladi: "osh narxi kerak, choy narxi kerak, chek chiqar". Lekin u narxlarni o'zi qayerdan olishni bilmaydi va konsolga o'zi yozmaydi. Bu ishlarni ijrochi qiladi. Ijrochi — Iteratsiya protokoli darsidagi qo'lda yozilgan for...of ning o'zi, faqat next() ga javob uzatadi.
6.2 Nega bu foydali: sinash oson
Generator "nima qilish kerak"ni aytadi, xolos. Shuning uchun uni haqiqiy narxlarsiz, soxta javoblar bilan sinash mumkin:
function* buyurtmaJarayoni() {
const osh = yield { tur: "narx", taom: "osh" };
const choy = yield { tur: "narx", taom: "ko'k choy" };
yield { tur: "chek", matn: `Jami: ${osh + choy} so'm` };
}
const sinov = buyurtmaJarayoni();
console.log(sinov.next().value);
console.log(sinov.next(100).value);
console.log(sinov.next(1).value);Konsolda:
{ tur: 'narx', taom: 'osh' }
{ tur: 'narx', taom: "ko'k choy" }
{ tur: 'chek', matn: "Jami: 101 so'm" }Biz ijrochi rolini qo'lda o'ynadik: "osh — 100, choy — 1" dedik va generator to'g'ri hisoblaganini ko'rdik. Haqiqiy narx bazasi, server yoki printer kerak bo'lmadi. Har qadamda generator nimani so'raganini ham tekshirdik.
6.3 Redux-saga va async/await ning ildizi
Aynan shu naqsh mashhur kutubxonalarning ichida turibdi:
- Redux-saga — React ilovalarida server so'rovlari kabi "yon ishlar"ni boshqaradigan kutubxona (Redux — ilova holatini saqlaydigan kutubxona, uni 18-qismda o'rganamiz; hozir bilish shart emas). Unda har jarayon — generator. Quyidagi parchadagi hamma nomni tushunish shart emas — faqat shaklga qarang:
yieldo'ng tomonida topshiriq, chap tomonida (const javob =) ijrochidan kelgan javob, xuddi bizningbuyurtmaJarayonidagidek:
function* buyurtmaSaga(action) {
const javob = yield call(api.buyurtmaBer, action.taom);
yield put({ type: "BUYURTMA_QABUL", javob });
}call(...) va put(...) hech narsani bajarmaydi — ular bizdagi { tur: "narx", ... } kabi topshiriq obyektlarini yasaydi. Saga ijrochisi so'rovni yuboradi, javob kelganda next(javob) bilan generatorni uyg'otadi. Sinovda esa haqiqiy server o'rniga soxta javob beriladi — xuddi bizning sinov kabi.
async/await— 11-qismdagi asinxron kodning asosiy sintaksisi. Undan oldin, 2014–2016 yillarda, dasturchilar xuddi shu naqshni ishlatgan (cokutubxonasi). Generator kutish kerak bo'lgan narsaniyieldqiladi. Ijrochi esa natija kelganda generatorninext(natija)bilan davom ettiradi.async/awaitshu g'oyani tilning o'ziga olib kirdi. Uniasync/awaitdarsida o'rganasiz — o'shanda bugungiijroEtni eslang.
7. Ko'p uchraydigan xatolar
7.1 Birinchi next() ga ma'lumot berish
«Birinchi next() argumenti yo'qoladi» bo'limidagi holat. Xato xabari yo'q — ma'lumot shunchaki yo'qoladi, hisob noto'g'ri chiqadi. Tuzatish: ikki tomonlama generatorni avval generator.next() bilan ishga tushiring, ma'lumotni ikkinchisidan boshlab bering.
7.2 yield va yield* ni chalkashtirish
yield ichkiGenerator() — generator obyektining o'zini beradi, yield massiv — massivning o'zini. Natijada Object [Generator] {} yoki ichma-ich massiv ko'rasiz. Tuzatish: ichidagilarni bittadan berish kerak bo'lsa — yield*.
7.3 yield* ga iterable bo'lmagan qiymat
function* soni() {
yield* 5;
}
console.log([...soni()]);Konsolda:
TypeError: number 5 is not iterable (cannot read property Symbol(Symbol.iterator))Tarjimasi: "5 soni iterable emas (Symbol.iterator xususiyatini o'qib bo'lmadi)". yield* ham protokol bilan ishlaydi — u qiymatdan [Symbol.iterator]() ni so'raydi (Iteratsiya protokolidagi new Map(obyekt) xatosi bilan bir oila). Ko'pincha sabab — yield* ga massiv o'rniga undefined kelishi (masalan, obyektda taomlar xususiyati yo'q). Tuzatish: bitta qiymat bo'lsa — yield; ro'yxat bo'lsa — haqiqatan ham ro'yxat kelayotganini tekshiring.
7.4 Ushlanmagan throw()
Generator ichida try / catch bo'lmasa, throw() xatoni chaqiruvchiga qaytaradi va dastur to'xtaydi («Xato to'xtagan yield da paydo bo'ladi» bo'limi). Generator ham tugaydi — uni qayta ishlatib bo'lmaydi. Tuzatish: generator xatoga "chidashi" kerak bo'lsa — yield ni try ichiga oling; aks holda xatoni tashqarida ushlang (try / catch / finally).
7.5 Tugagan generatordan javob kutish
return() yoki ushlanmagan xatodan keyin generator "Tugagan". Unga next(qiymat) bilan yangi ma'lumot berish hech narsa qilmaydi — har safar { value: undefined, done: true }. Tuzatish: yangi suhbat uchun generator funksiyani qayta chaqiring.
8. Mashqlar
1-mashq (oson): Kunlik menyu
nonushta() ("tuxum", "choy") va tushlik() ("osh", "salat") generatorlarini yozing. Keyin kunlikMenyu() generatori ikkalasini yield* bilan ulasin va oxirida "shirinlik" ni oddiy yield bilan qo'shsin.
Yechim
function* nonushta() {
yield "tuxum";
yield "choy";
}
function* tushlik() {
yield "osh";
yield "salat";
}
function* kunlikMenyu() {
yield* nonushta();
yield* tushlik();
yield "shirinlik";
}
console.log([...kunlikMenyu()]);Konsolda:
[ 'tuxum', 'choy', 'osh', 'salat', 'shirinlik' ]yield* va yield bitta generatorda bemalol aralashadi: biri butun ro'yxatni topshiradi, ikkinchisi bitta qiymat qo'shadi.
2-mashq (o'rta): Ichma-ich massivni tekislash
tekisla(massiv) generatorini yozing: u istalgan chuqurlikdagi ichma-ich massivning hamma oddiy qiymatlarini bittadan bersin. Element massivmi — Array.isArray(x) bilan tekshiring. Rekursiya va yield* dan foydalaning.
const buyurtmalar = ["osh", ["manti", ["somsa", "choy"]], "salat"];
console.log([...tekisla(buyurtmalar)]);
// [ 'osh', 'manti', 'somsa', 'choy', 'salat' ]Yechim
function* tekisla(massiv) {
for (const element of massiv) {
if (Array.isArray(element)) {
yield* tekisla(element);
} else {
yield element;
}
}
}
const buyurtmalar = ["osh", ["manti", ["somsa", "choy"]], "salat"];
console.log([...tekisla(buyurtmalar)]);Konsolda:
[ 'osh', 'manti', 'somsa', 'choy', 'salat' ]Massiv bo'lsa — o'ziga topshiradi (rekursiya), oddiy qiymat bo'lsa — beradi. Bu flat(Infinity) ning dangasa nusxasi: flat darhol yangi massiv yasaydi, tekisla esa qiymatlarni so'ralganda bittadan beradi.
3-mashq (qiyin): Baholarning o'rtachasi
«Bahor» mehmonlari 1 dan 5 gacha baho qo'yadi. ortacha() generatorini yozing: u har next(baho) da shu paytgacha kelgan baholarning o'rtachasini qaytarsin. Birinchi, ishga tushiruvchi next() undefined qaytarsin.
const reyting = ortacha();
reyting.next();
console.log(reyting.next(5).value); // 5
console.log(reyting.next(4).value); // 4.5
console.log(reyting.next(3).value); // 4Yechim
function* ortacha() {
let jami = 0;
let soni = 0;
let natija;
while (true) {
const baho = yield natija;
jami += baho;
soni++;
natija = jami / soni;
}
}
const reyting = ortacha();
reyting.next();
console.log(reyting.next(5).value); // 5
console.log(reyting.next(4).value); // 4.5
console.log(reyting.next(3).value); // 4natija boshida undefined — birinchi yield natija shuni beradi. Keyin har baho kelganda jami va soni yangilanadi va yangi o'rtacha keyingi yield da chiqadi. Holat (jami, soni) generator ichida yashirin — tashqi kod faqat baho beradi va javob oladi.
4-mashq: Vazifalar qadami — iterator bitta qatorga, branch'ni yakunlash
O'z iterable obyektingizni yasash darsida refactor/iteratsiya branch'ida VazifalarRoyxati ga 15 qatorli qo'lda yozilgan iterator qo'shgan edingiz. Endi uni generator va yield* bilan qayta yozing. Xulq o'zgarmasin. So'ng branch'ni main ga qo'shing.
Yechim
VazifalarRoyxati dagi butun [Symbol.iterator]() { ... } metodi o'rniga:
*[Symbol.iterator]() {
yield* this.#vazifalar;
}Bitta qator. * — metod generator; yield* — ishni ichki massivning o'z iteratoriga topshiradi. this to'g'ri ishlaydi — generator metod royxatda chaqiriladi. Qo'lda yozilgan variantdagi hamma narsa saqlandi: { value, done }, o'zini qaytaradigan [Symbol.iterator](), break da return(), hatto massivni "jonli" ko'rish — chunki yield* massivning o'z iteratoriga topshirdi.
Eski va yangi variant bir xil ishlashini Node'da tekshiring:
class QoldaRoyxat {
#vazifalar = ["Non olish", "DOM darsini takrorlash"];
qosh(matn) {
this.#vazifalar.push(matn);
}
[Symbol.iterator]() {
const vazifalar = this.#vazifalar;
let i = 0;
return {
next() {
if (i < vazifalar.length) {
return { value: vazifalar[i++], done: false };
}
return { value: undefined, done: true };
},
[Symbol.iterator]() {
return this;
},
};
}
}
class GeneratorRoyxat {
#vazifalar = ["Non olish", "DOM darsini takrorlash"];
qosh(matn) {
this.#vazifalar.push(matn);
}
*[Symbol.iterator]() {
yield* this.#vazifalar;
}
}
for (const Klass of [QoldaRoyxat, GeneratorRoyxat]) {
const royxat = new Klass();
const iterator = royxat[Symbol.iterator]();
const birinchi = iterator.next().value;
royxat.qosh("Choy olish");
const qolgani = [...iterator];
console.log(Klass.name, birinchi, qolgani.length);
}Konsolda:
QoldaRoyxat Non olish 2
GeneratorRoyxat Non olish 2Ikkalasi ham aylanish o'rtasida qo'shilgan "Choy olish" ni ko'rdi (qolgani — 2 ta). Xulq bir xil — farqi faqat kod hajmida.
Brauzerda butun ilovani sinab chiqing:
- Uch vazifa, "2 ta vazifa qoldi, 1 ta bajarildi"; qo'shish, belgilash, «Faol» filtrida belgilash (fokus qo'shniga), tahrirlash (
Enter,Esc), o'chirish, takror va bo'sh matn xatolari — avvalgidek. - Console'da:
[...holat.vazifalar].length— vazifalar soni;holat.vazifalar[Symbol.iterator]()— Node formatidaObject [Generator] {}. Chrome konsoli uni generator metod nomi bilan,[Symbol.iterator] {<suspended>}ko'rinishida chiqaradi — ma'nosi bir xil: to'xtab turgan generator.
Commit va branch'ni yakunlash:
git add assets/js/vazifalar.js
git commit -m "Ro'yxat: iteratorni generator bilan yoz"
git switch main
git merge refactor/iteratsiya
git branch -d refactor/iteratsiya
git pushBu ikki darsdagi o'zgarishlar:
| Dars | vazifalar.js da |
|---|---|
| O'z iterable'ingiz | [Symbol.iterator]() — qo'lda, 15 qator |
| shu dars | *[Symbol.iterator]() { yield* this.#vazifalar; } |
Pages bir-ikki daqiqada yangilanadi. Foydalanuvchi farq sezmaydi — royxat esa endi tilning umumiy "aylanish tili"da gapiradi.
9. Real ishda
- Redux-saga. Yirik React ilovalarida (bank, internet-do'kon, yetkazib berish xizmatlari) murakkab jarayonlar — to'lov, qayta urinish, bekor qilish — saga generatorlari bilan yoziladi. «Generator va "ijrochi"» bo'limidagi naqsh — uning yuragi.
- Daraxt bo'ylab yurish. Fayl tizimi, DOM, menyu kategoriyalari, izohlar va ularga javoblar — hammasi daraxt.
yield*bilan rekursiv aylanish — eng qisqa va dangasa yo'l. Algoritmlar qismida (14-qism) daraxt va graflarni ham shunday aylanasiz. - Kutubxona ichlari. Babel (yangi JavaScript'ni eski brauzerlar tushunadigan shaklga o'giruvchi vosita) va TypeScript (JavaScript'ga turlar qo'shadigan til) — ikkalasini kursda alohida o'rganamiz. Ular eski brauzerlar uchun
async/awaitni aynan generator + ijrochi ko'rinishiga o'girgan. Bugun ham eski loyihalarning yig'ilgan kodida__generatoryordamchisini uchratasiz. - Intervyu. "
yield*nima qiladi?", "next()ga argument berish nima beradi?", "async/awaitgeneratorlar bilan qanday bog'liq?" — o'rta va yuqori darajadagi intervyu savollari.
Xulosa
yield* iterable— ishni topshirish: ichki iterable'ning hamma qiymatini bittadan beradi; natijasi — ichki generatorningreturnqiymati.yield*+ rekursiya — ichma-ich tuzilmani (daraxtni) dangasa aylanishning eng qisqa yo'li.const x = yield y—ytashqariga chiqadi, keyinginext(qiymat)dagi qiymatxga tushadi. Birinchinext()argumenti yo'qoladi — u faqat ishga tushiradi.return(x)— to'xtagan joyda yakunlaydi;throw(xato)— to'xtagan joyda xato chiqaradi (ichkarida ushlansa — davom etadi).- Generator + ijrochi — korutin naqshi: generator topshiriq beradi, ijrochi bajaradi. Redux-saga va
async/awaitshu g'oyadan o'sib chiqqan.
Keyingi dars: Iterator helpers — iteratorlarda ham map, filter, take bor: ular massiv yasamay, dangasa ishlaydi.
Manbalar
- MDN: "yield*", "Generator.prototype.next()", "Generator.prototype.return()", "Generator.prototype.throw()" — developer.mozilla.org
- redux-saga hujjatlari: "Beginner Tutorial", "Declarative Effects" — redux-saga.js.org
- ECMAScript spetsifikatsiyasi: "Generator Objects", "yield*" — tc39.es/ecma262
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!