Muntazam funktsiyalar faqat bitta, yagona qiymatni qaytaradi (yoki hech narsa).
Generatorlar talabga binoan bir nechta qiymatlarni, ehtimol cheksiz koâp qiymatlarni qaytarishlari mumkin. Ular maâlumotlar oqimlarini osongina yaratishga imkon beradigan ketma-ket saraluvchan bilan juda yaxshi ishlaydi.
Generator funktsiyalari
Generator yaratish uchun biz maxsus sintaksis konstruktsiyasiga muhtojmiz: âgenerator funktsiyasiâ deb nomlangan function*.
Bunga oâxshaydi:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
generateSequence() chaqirilganda, u kodni bajarmaydi. Buning oârniga u âgeneratorâ deb nomlangan maxsus obyektni qaytaradi.
// "generator funktsiya" "generator obyektni" yaratadi
let generator = generateSequence();
alert(generator); // [object Generator]
Generator obyekti âmuzlatilgan funktsiya chaqiruviâ sifatida qabul qilinishi mumkin:
Yaratilgandan soâng, kodni bajarishning eng boshida toâxtatiladi.
Generatorning asosiy usuli next(). Chaqirilganda, u eng yaqin yield <value> ifodagacha bajarilishni davom ettiradi. Keyin ijro toâxtaydi va qiymat tashqi kodga qaytariladi.
Masalan, bu yerda biz generatorni yaratamiz va uning dastlabki qiymatini olamiz:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
let one = generator.next();
alert(JSON.stringify(one)); // {value: 1, done: false}
next() natijasi har doim obyekt:
value: olingan qiymat.done:falseagar kod hali tugamagan boâlsa, aks holdatrue.
Hozirda biz faqat birinchi qiymatni oldik:
Yana generator.next() ni chaqiraylik. U ijro etilishini davom ettiradi va keyingi yield ni qaytaradi:
let two = generator.next();
alert(JSON.stringify(two)); // {value: 2, done: false}
Va agar biz uni uchinchi marta chaqirsak, unda funktsiya tugaydigan return ifodasiga yetadi:
let three = generator.next();
alert(JSON.stringify(three)); // {value: 3, done: true}
Endi generator tugadi. Biz buni yakunlangan natija sifatida done:true va jarayonning value:3 qiymatidan koârishimiz kerak.
generator.next() yangi chaqiruvlari endi mantiqiy emas. Agar biz ularni qilsak, ular xuddi shu obyektni qaytaradilar: {done: true}.
Generatorni âorqaga qaytarishâ imkoni yoâq. Ammo generateSequence() ni chaqirib boshqasini yaratishimiz mumkin.
Hozircha eng muhim narsani tushunish kerakki, generator funktsiyalari, odatdagi funktsiyalardan farqi oâlaroq, kodni bajarmaydi. Ular âgenerator zavodlariâ sifatida xizmat qiladi. function* ishga tushirish generatorni qaytaradi va biz undan qiymatlarni soâraymiz.
function* f(â¦) yoki function *f(â¦)?Bu kichik diniy savol, ikkala sintaksis ham toâgâri.
Ammo odatda birinchi sintaksisga afzallik beriladi, chunki * yulduzi bu generator funktsiyasi ekanligini anglatadi, u ismni emas, balki turini tavsiflaydi, shuning uchun u function kalit soâziga ulanishi kerak.
Generatorlar ketma-ket saraluvchandir
Ehtimol, next() usulini koârib chiqishni taxmin qilganingizdek, generatorlar ketma-ket saraluvchan.
Biz qiymatlar boâyicha for..of orqali tsiklashimiz mumkin:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1, then 2
}
Bu generatorlar bilan ishlashning .next().value chaqirishdan koâra ancha yaxshi usulidir, toâgârimi?
â¦Iltimos, diqqat qiling: yuqoridagi misolda 1, keyin 2 koârsatilgan va barchasi shu. 3 koârsatilmaydi!
Buning sababi, takrorlash uchun oxirgi value, done: true ni eâtiborsiz qoldiradi. Shunday qilib, agar biz barcha natijalarni for..of bilan koârsatishni istasak, ularni yield bilan qaytarishimiz kerak:
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1, so'ng 2, keyin 3
}
Tabiiyki, generatorlar takrorlanadigan boâlgani uchun biz barcha tegishli funktsiyalarni chaqira olamiz, masalan. tarqatish operatori ...:
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let sequence = [0, ...generateSequence()];
alert(sequence); // 0, 1, 2, 3
Yuqoridagi kodda ...generateSequence() ketma-ket saraluvchanlarni narsalar massiviga aylantiradi ("rest-parameters-spread-operator" maqolasi topilmadi boâlimidagi tarqatish operatori haqida koâproq maâlumot oling)
Ketma-ket saraluvchanlarning oârniga generatorlardan foydalanish
Bir muncha vaqt oldin, Tsiklda koârib chiqish imokniyatiga ega maâlumot turlari boâlimida biz qiymatlarni from-to ga qaytaradigan, ketma-ket saraluvchan range obyektini yaratdik.
Keling, kodni eslab qolaylik:
let range = {
from: 1,
to: 5,
// for..of bu usulni boshida bir marta chaqiradi
[Symbol.iterator]() {
// ...ketma-ket saraluvchan obyektini qaytaradi:
// oldinga, for..of faqat shu obyekt bilan ishlaydi, undan keyingi qiymatlarni so'raydi
return {
current: this.from,
last: this.to,
// next() for..of tsikldan har bir iteratsiyada chaqiriladi
next() {
// u qiymatni obyekt sifatida qaytarishi kerak {done:.., value :...}
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
// iteration over range returns numbers from range.from to range.to
alert([...range]); // 1,2,3,4,5
Ketma-ket saraluchanni yaratish uchun generatordan foydalanish juda ham oqlangan:
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
let sequence = [...generateSequence(1,5)];
alert(sequence); // 1, 2, 3, 4, 5
â¦Ammo, agar biz odatiy range obyektini saqlamoqchi boâlsak nima boâladi?
Symbol.iterator-ni generatorga aylantirish
Biz generatorni Symbol.iterator sifatida taqdim etish orqali har ikkala dunyodan eng yaxshisini olishimiz mumkin:
let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // [Symbol.iterator]: function*() uchun qisqartirma
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
alert( [...range] ); // 1,2,3,4,5
range obyekti endi ketma-ket saraluvchan.
Bu juda yaxshi ishlaydi, chunki qachon range[Symbol.iterator] chaqiriladi:
- u obyektni qaytaradi (endi generator)
.next()usuliga ega (ha, generatorda mavjud)- qiymatlarni
{value: ..., done: true/false}shaklida qaytaradi (tekshiring, aynan nimani generator bajaradi).
Bu tasodif emas, albatta. Generatorlar ketma-ket saraluvchanlarni osonlashtirishni maqsad qilib qoâyishgan, shuning uchun biz buni koârishimiz mumkin.
Generatordagi soânggi variant asl takrorlanadigan kodga qaraganda ancha ixcham va bir xil funktsiyani saqlaydi.
Yuqoridagi misollarda biz cheklangan ketma-ketliklarni yaratdik, ammo abadiy qiymatlarni beradigan generatorni ham yaratishimiz mumkin. Masalan, soxta tasodifiy sonlarning tugamaydigan ketma-ketligi.
Buning uchun for..of ning break usuli kerak boâladi, aks holda bu halqa abadiy takrorlanib, osilib qoladi
Generatorlar tarkibi
Generator tarkibi â generatorlarni bir-biriga shaffof ravishda âjoylashtirishâ imkonini beradigan generatorlarning oâziga xos xususiyati.
Masalan, biz ketma-ketlikni yaratmoqchimiz:
0..9raqamlari (belgilar kodlari 48â¦57),- keyin alifbo harflari
a..z(belgilar kodlari 65â¦90) - keyin katta harflar
A..Z(belgilar kodlari 97â¦122)
Keyin biz undan belgilarni tanlash orqali parollar yaratishni rejalashtirmoqdamiz (sintaksis belgilarini ham qoâshishi mumkin), lekin oldin ketma-ketlikni yaratishimiz kerak.
Bizda allaqachon function* generateSequence(start, end) mavjud. Keling, uni uchta ketma-ketlikni yetkazib berish uchun qayta ishlataylik, birgalikda ular bizga kerakli narsadir.
Muntazam funktsiyalarda bir nechta boshqa funktsiyalar natijalarini birlashtirish uchun biz ularni chaqiramiz, natijalarni saqlaymiz va soângra birlashtiramiz.
Generatorlar uchun biz quyidagilarni yaxshiroq qilishimiz mumkin:
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* generatePasswordCodes() {
// 0..9
yield* generateSequence(48, 57);
// A..Z
yield* generateSequence(65, 90);
// a..z
yield* generateSequence(97, 122);
}
let str = '';
for(let code of generatePasswordCodes()) {
str += String.fromCharCode(code);
}
alert(str); // 0..9A..Za..z
Misol uchun maxsus yield* direktivasi kompozitsiya uchun javobgardir. Bu ijroni boshqa generatorga topshiradi. Yoki, oddiy qilib aytganda, u generatorlarni bajaradi va shaffof ravishda ularning hosilini tashqarida, xuddi chaqiruvchi generatorning oâzi bajargandek qilib uzatadi.
Natija xuddi biz ichki oârnatilgan generatorlardan kodni kiritganimiz bilan bir xil:
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* generateAlphaNum() {
// yield* generateSequence(48, 57);
for (let i = 48; i <= 57; i++) yield i;
// yield* generateSequence(65, 90);
for (let i = 65; i <= 90; i++) yield i;
// yield* generateSequence(97, 122);
for (let i = 97; i <= 122; i++) yield i;
}
let str = '';
for(let code of generateAlphaNum()) {
str += String.fromCharCode(code);
}
alert(str); // 0..9A..Za..z
Generator tarkibi â bu bir generatorning oqimini boshqasiga kiritishning tabiiy usuli.
Ichki generatordan qiymatlar oqimi cheksiz boâlsa ham ishlaydi. Bu oddiy va oraliq natijalarni saqlash uchun qoâshimcha xotiradan foydalanilmaydi.
âyieldâ bu ikki tomonlama yoâl
Shu paytgacha generatorlar âsteroidlardagi ketma-ket saraluvchanâ kabi edi. Va shuning uchun ular tez-tez ishlatiladi.
Ammo aslida ular ancha kuchli va moslashuvchan.
Buning sababi shundaki, yield ikki tomonlama yoâldir: u nafaqat natijani tashqariga qaytaradi, balki generator ichidagi qiymatni ham oâtkazishi mumkin.
Buning uchun argument bilan generator.next(arg) ni chaqirishimiz kerak. Ushbu argument yield natijasiga aylanadi.
Keling, bir misolni koârib chiqaylik:
function* gen() {
// Savolni tashqi kodga o'tkazing va javobni kuting
let result = yield "2 + 2?"; // (*)
alert(result);
}
let generator = gen();
let question = generator.next().value; // <-- yield qiymatni qaytaradi
generator.next(4); // --> natijani generatorga o'tkazish
- Birinchi chaqiruv
generator.next()har doim argumentsiz. U ijroni boshlaydi va birinchiyield(â2+2?â) natijasini qaytaradi. Shu nuqtada generator bajarilishini toâxtatadi (hali ham oâsha satrda). - Keyin, yuqoridagi rasmda koârsatilgandek,
yieldnatijasi chaqiruv kodidagiquestionoâzgaruvchanga tushadi. generator.next(4)da generator qayta ishlaydi va natijada4oladi:let result = 4.
Iltimos, eâtibor bering, tashqi kod darhol next(4) chaqirish shart emas. Qiymatni hisoblash uchun vaqt kerak boâlishi mumkin. Bu shuningdek yaroqli kod:
// bir muncha vaqt o'tgach generatorni davom ettiring
setTimeout(() => generator.next(4), 1000);
Sintaksis biroz gâalati tuyulishi mumkin. Funktsiya va chaqiruv kodi qiymatlarni bir-biriga oâtkazishi juda kam uchraydi. Ammo aynan shu narsa sodir boâlmoqda.
Ishni yanada aniqroq qilish uchun yana bir misol, qoâshimcha chaqiruvlar:
function* gen() {
let ask1 = yield "2 + 2 = ?";
alert(ask1); // 4
let ask2 = yield "3 * 3 = ?"
alert(ask2); // 9
}
let generator = gen();
alert( generator.next().value ); // "2 + 2 = ?"
alert( generator.next(4).value ); // "3 * 3 = ?"
alert( generator.next(9).done ); // true
Ijro etiladigan rasm:
- Birinchi
.next()bajarilishini boshlaydi⦠Birinchiyieldga yetadi. - Natijada tashqi kodga qaytariladi.
- Ikkinchi
.next(4)birinchiyieldnatijasida4ni generatorga qaytaradi va bajarilishini davom ettiradi. - â¦U generatorning chaqiruvi natijasi boâlgan ikkinchi
yieldga yetadi. - Uchinchi
.next(9)ikkinchiyieldnatijasida generatorga9oâtadi va funktsiya oxirigacha bajarilishini davom ettiradi, shunday qilibdone: true.
Bu âstol tennisâ oâyiniga oâxshaydi. Har bir next(value) (birinchisidan tashqari) generatorga qiymatni oâtkazadi, bu joriy yield natijasiga aylanadi va keyin keyingi yield natijasini qaytaradi.
generator.throw
Yuqoridagi misollarda kuzatganimizdek, tashqi kod yield natijasida generatorga qiymat oâtkazishi mumkin.
â¦Ammo u yerda ham xato boshlanishi (tashlanishi) mumkin. Bu tabiiy, chunki xato â bu natija.
Xatolikni yield ga oâtkazish uchun biz generator.throw(err) deb nomlashimiz kerak. Bunday holda, err shu yield bilan bir satrga tashlanadi.
Masalan, "2 + 2?" hosilasi xatolikka olib keladi:
function* gen() {
try {
let result = yield "2 + 2 = ?"; // (1)
alert("Ijro etish bu yerga yetib bormaydi, chunki istisno yuqoriga tashlangan");
} catch(e) {
alert(e); // xatoni ko'rsatadi
}
}
let generator = gen();
let question = generator.next().value;
generator.throw(new Error("Javob mening ma'lumotlar bazasida topilmadi")); // (2)
(2) satridagi generatorga tashlangan xato (1) satrida yield bilan istisnoga olib keladi. Yuqoridagi misolda try..catch uni ushlaydi va koârsatadi.
Agar biz uni ushlamasak, unda har qanday istisno kabi, u generatorni chaqiruv kodiga âtushiradiâ.
Chaqiruv kodining joriy satri (2) deb belgilangan generator.throw satridir. Shunday qilib, biz uni shu yerda ushlashimiz mumkin:
function* generate() {
let result = yield "2 + 2?"; // Ushbu satrda xatolik yuz berdi
}
let generator = generate();
let question = generator.next().value;
try {
generator.throw(new Error("Javob mening ma'lumotlar bazasida topilmadi"));
} catch(e) {
alert(e); // xatoni ko'rsatadi
}
Agar biz u yerda xatoga yoâl qoâymasak, u odatdagidek, tashqi chaqiruv kodiga tushadi (agar mavjud boâlsa) va agar tutilmasa, skriptni oâldiradi.
Xulosa
- Generatorlar generator funktsiyalari tomonidan yaratiladi
function*(â¦) {â¦}. - Generatorlar ichida (faqat)
yieldoperatori mavjud. - Tashqi kod va generator
next/yieldchaqiruvlar orqali natijalarni almashtirishi mumkin.
Zamonaviy JavaScript-da generatorlar kamdan kam qoâllaniladi. Ammo baâzida ular foydali boâladi, chunki bajarilish paytida funktsiyani chaqirish kodi bilan maâlumot almashish qobiliyati juda noyobdir.
Shuningdek, keyingi bobda biz for tsiklida mos kelmaydigan maâlumotlar oqimlarini oâqish uchun ishlatiladigan asinxron generatorlarni oârganamiz.
Veb-dasturlashda biz koâpincha oqim maâlumotlari bilan ishlaymiz, masalan, sahifalangan natijalarni olish kerak, shuning uchun bu juda muhim holat.
Izohlar
<code>yorlig'ini ishlating, bir nechta satrlar uchun - ularni<pre>yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepenâ¦)