Кадимки функциялар бир гана маанини кайтарат (же эч нерсе).
Генераторлор зарыл болгон учурда биринин артынан бири бир нече баалуулуктарды бере алат. Генераторлор кайталануучу түзүлүштөр менен сонун иштешет жана маалымат агымдарын түзүүнү жеңилдетет.
генератор функциясы
Генераторду жарыялоо үчүн атайын синтаксистик конструкция колдонулат: function*
, ал “генератор функциясы” деп аталат.
Бул төмөнкүдөй көрүнөт:
@A@function* generateSequence() {
yield 1;
yield 2;
return 3;
}@A@
Генератордун функциялары кадимки функциялардан башкача иштейт. Мындай функция чакырылганда, ал өзүнүн кодун аткарбайт. Анын ордуна, анын аткарылышын көзөмөлдөө үчүн "генератор" деп аталган атайын объектти кайтарат.
Мына, карагыла:
@A@function* generateSequence() {
yield 1;
yield 2;
return 3;
}
// "функция-генератор" создаёт объект "генератор"
let generator = generateSequence();
alert(generator); // [object Generator]@A@
Функция коду аткарыла элек:
Негизги генератор ыкмасы болуп саналат next()
. Чакырганда, ал жакынкы нускамага чейин кодду аткара баштайт yield <значение>
(маани жок болушу мүмкүн, бул учурда ал undefined
. Ага жеткенде, yield
функциянын аткарылышы токтотулат жана тиешелүү маани тышкы кодго кайтарылат:
Методдун натыйжасы next()
ар дайым эки касиетке ээ объект болуп саналат:
value
: баштап маанисиyield
.done
:true
эгерде функция аткарылып бүтсө, болбосоfalse
.
Мисалы, бул жерде биз генератор түзөбүз жана анын кайтаруу маанилеринин биринчисин алабыз:
@A@function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
let one = generator.next();
alert(JSON.stringify(one)); // {value: 1, done: false}@A@
Учурда биз биринчи гана маанини алдык, экинчи сапта функциянын аткарылышы токтотулду:
Экинчи чалуу generator.next()
коддун аткарылышын улантат жана төмөнкүлөрдүн натыйжасын берет yield
:
@A@let two = generator.next();
alert(JSON.stringify(two)); // {value: 2, done: false}
Акыр-аягы, акыркы чакыруу функцияны бүтүрүп, натыйжаны кайтарат return
:
let three = generator.next();
alert(JSON.stringify(three)); // {value: 3, done: true}@A@
Генератор азыр бүттү. Биз аны менчигинен көрүп , акыркы натыйжа катары done:true
иштете алабыз .value:3
Жаңы чакырыктардын generator.next()
мааниси жок. Бирок, эгерде алар болсо, алар ката кетирбейт, бирок ошол эле объектти кайтарат: {done: true}
.
function* f(…)
же function *f(…)
?Эч кандай айырмасы жок, эки синтаксиси тең туура.
Бирок, адатта, биринчи вариантка артыкчылык берилет, анткени жылдызча анын аталышына эмес, жарыяланып жаткан объекттин түрүнө ( function*
- "генератор функциясы") тиешелүү, ошондуктан аны сөздүн жанына коюу жөндүү function
.
Генераторлорду санап чыгуу
Методдун бар экендигин сиз буга чейин болжолдогондой эле next()
, генераторлор кайталануучу объекттер.
Алар кайтарган баалуулуктар аркылуу кайталанса болот for..of
:
@A@function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1, затем 2
}@A@
колдонуудан алда канча сулуу көрүнөт .next().value
, туурабы?
1
…Бирок көңүл буруңуз: жогорудагы мисал , андан кийин маанисин чыгарат 2
. Маани 3
көрсөтүлбөйт!
Себеби, кайра кайталоо for..of
бул жердеги акыркы маанини этибарга албайт done: true
. Ошондуктан, биз аркылуу итерациялоодо бардык баалуулуктарга ээ болгубуз келсе for..of
, анда аларды төмөнкү аркылуу кайтарышыбыз керек yield
:
@A@function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1, затем 2, затем 3
}@A@
Генераторлор кайталануучу объект болгондуктан, биз алар менен байланышкан бардык функцияларды колдоно алабыз, мисалы, жайылтуу оператору ...
:
@A@function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let sequence = [0, ...generateSequence()];
alert(sequence); // 0, 1, 2, 3@A@
Жогорудагы коддо ...generateSequence()
кайталануучу генератор объектисин элементтердин массивине айландырат (спред оператору жөнүндө көбүрөөк маалымат алуу үчүн, калдык параметрлери жана жайылтуу оператору бөлүмүн караңыз )
Итерабилдүүлүк үчүн генераторлорду колдонуу
Бир нече убакыт мурун, Итеративдер бөлүмүндө биз range
маанилерди кайтаруучу кайталануучуну түздүк from..to
.
Келгиле, кодду эстеп көрөлү:
@A@let range = {
from: 1,
to: 5,
// for..of range вызывает этот метод один раз в самом начале
[Symbol.iterator]() {
// ...он возвращает перебираемый объект:
// далее for..of работает только с этим объектом, запрашивая следующие значения
return {
current: this.from,
last: this.to,
// next() вызывается при каждой итерации цикла for..of
next() {
// нужно вернуть значение как объект {done:.., value :...}
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
// при переборе объекта range будут выведены числа от range.from до range.to
alert([...range]); // 1,2,3,4,5@A@
Генератор функциясын итерациялоо үчүн колдоно алабыз Symbol.iterator
.
Мына ошол эле range
, бирок бир топ компакттуу итератор менен:
@A@let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // краткая запись для [Symbol.iterator]: function*()
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
alert( [...range] ); // 1,2,3,4,5@A@
Бул иштейт, анткени ал range[Symbol.iterator]()
азыр генераторду кайтарат жана анын ыкмалары так ал күткөндөй for..of
:
- анын ыкмасы бар
.next()
- формадагы маанилерди кайтарат
{value: ..., done: true/false}
Бул, албетте, кокустук эмес. Генераторлор JavaScript тилине кошулган, бул бир жагынан кайталануучу объекттерди түзүүнү жеңилдетүү үчүн.
Генератордун варианты баштапкы кайталануучу вариантка караганда бир топ кыска range
жана ошол эле функцияны сактап калат.
Жогорудагы мисалдарда биз чектүү тизмектерди түздүк, бирок биз чексиз маанилерди кайтара турган генератор жасай алабыз. Мисалы, псевдококустук сандардын чексиз ырааттуулугу.
Албетте, мындай генератордун үстүнөн циклде бизге break
(же ) керек болот, антпесе цикл чексиз уланып, скрипт “илип” калат.return
for..of
Генераторлордун курамы
Композиттик генераторлор генераторлордун өзгөчө өзгөчөлүгү болуп саналат, ал генераторлорду бири-бирине ачык "киргизүүгө" мүмкүндүк берет.
Мисалы, бизде сандардын ырааттуулугун түзүү функциясы бар:
@A@function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}@A@
Биз аны бир кыйла татаал ырааттуулукту жаратууда колдонгубуз келет:
- биринчи сандар
0..9
(48…57 белги коддору менен) - андан кийин баш тамгалар
A..Z
(символдун коддору 65…90) - андан кийин алфавиттин тамгалары
a..z
(символдун коддору 97…122)
Биз сырсөздөрдү түзүү үчүн ушундай ырааттуулукту колдоно алабыз, андан символдорду тандай алабыз (балким, дагы тыныш белгилерин кошуу), бирок адегенде аны түзүү керек.
Кадимки функцияда, бир нече башка функциялардын натыйжаларын бириктирүү үчүн, биз аларды чакырып, аралык натыйжаларды сактап, анан аягында бириктиребиз.
yield*
Генераторлор үчүн генераторлорду бири-бирине "уялоого" мүмкүндүк берген атайын синтаксис бар (аларды түзүү).
Бул жерде курамы менен генератор болуп саналат:
@A@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@A@
Директива аткарууну башка генераторго тапшырат yield*
. yield* gen
Бул термин генераторду итерациялоону gen
жана анын чыгышын ачык-айкын түрдө сыртка багыттоону билдирет . баалуулуктар тышкы генератор тарабынан түзүлгөн сыяктуу.
Натыйжа биз уя генераторлорунун кодун киргизген сыяктуу эле:
@A@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@A@
Генератордун курамы - бул бир генератордун чыгышын экинчисинин агымына киргизүүнүн табигый жолу. Ал аралык натыйжаларды сактоо үчүн кошумча эстутумду колдонбойт.
түшүм - эки багытта жол
Ушул учурга чейин генераторлор баалуулуктарды түзүү үчүн атайын синтаксиси бар, кайталануучу объекттерге окшош. Бирок, чынында, алар алда канча күчтүү жана ийкемдүү.
Кеп болуп саналат yield
- эки багытта жол: ал сыртка гана натыйжаны кайтарып тим болбостон, ошондой эле генератор үчүн сырттан маанисин өткөрүп бере алат.
Бул үчүн биз generator.next(arg)
аргумент менен чакыруубуз керек. Бул аргумент натыйжа болуп калат yield
.
Муну бир мисал менен көрсөтөлү:
@A@function* gen() {
// Передаём вопрос во внешний код и ожидаем ответа
let result = yield "2 + 2 = ?"; // (*)
alert(result);
}
let generator = gen();
let question = generator.next().value; // <-- yield возвращает значение
generator.next(4); // --> передаём результат в генератор@A@
- Биринчи чалуу
generator.next()
ар дайым аргументсиз болот, ал аткарылып баштайт жана биринчинин жыйынтыгын кайтаратyield "2+2=?"
. Бул учурда, генератор аткарууну токтотот. - Андан кийин, жогорудагы сүрөттө көрсөтүлгөндөй, натыйжа
yield
өзгөрмөдөгү тышкы кодго өткөрүлүп берилетquestion
. - Аткаруу учурунда
generator.next(4)
генератор кайра улантат жана4
натыйжада тапшырмадан чыгат:let result = 4
.
Дароо чалуу үчүн тышкы код талап кылынбайт next(4)
. Ага убакыт керек болушу мүмкүн. Бул көйгөй эмес, генератор күтөт.
Мисалы:
@A@// возобновить генератор через некоторое время
setTimeout(() => generator.next(4), 1000);@A@
Көрүнүп тургандай, кадимки функциялардан айырмаланып, генератор next/yield
.
Эмне болуп жатканын айкыныраак кылуу үчүн, бул жерде көбүрөөк чалуулар менен дагы бир мисал:
@A@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@A@
Аткаруу сүрөтү:
- Биринчиси
.next()
аткара баштайт... Биринчисине жететyield
. - Натыйжа сырткы кодго кайтарылат.
- Экинчиси биринчинин натыйжасында генераторго кайра
.next(4)
өтүп , аткарууну улантат.4
yield
- ... Бул экинчисине келет
yield
, натыйжасы болот.next(4)
. - Үчүнчүсү экинчинин натыйжасында генераторго
next(9)
өтөт жана аткарууну улантат, ал функциянын бүтүшү менен аяктайт, ошондуктан .9
yield
done: true
Мындай "пинг-понг" болуп чыкты: ар бир адам next(value)
генераторго токтун натыйжасы болгон маанини берет yield
, аткарууну улантат жана кийинкиден туюнтманы алат yield
.
генератор.таштоо
Жогорудагы мисалдарда көргөнүбүздөй, тышкы код генераторго натыйжа катары маани бере алат yield
.
...Бирок сиз жыйынтыкты гана эмес, катаны баштасаңыз да болот. Бул табигый нерсе, анткени ката бир натыйжа болуп саналат.
Ката жөнөтүү үчүн yield
, биз чалышыбыз керек generator.throw(err)
. Бул учурда, өзгөчөлүк err
менен сапка ыргытылат yield
.
Мисалы, бул yield "2 + 2 = ?"
катага алып келет:
@A@function* gen() {
try {
let result = yield "2 + 2 = ?"; // (1)
alert("Выполнение программы не дойдёт до этой строки, потому что выше возникнет исключение");
} catch(e) {
alert(e); // покажет ошибку
}
}
let generator = gen();
let question = generator.next().value;
generator.throw(new Error("Ответ не найден в моей базе данных")); // (2)@A@
Сапта генераторго ыргытылган ката сапта (2)
өзгөчө абалга алып келет . Жогорудагы мисалда, ал аны кармап, көрсөтөт.(1)
yield
try..catch
Эгерде биз аны кармагыбыз келбесе, анда ал кадимки өзгөчөлүк сыяктуу эле, генератордон тышкы кодго "түшүп кетет".
Чалуу кодунун учурдагы сабы менен generator.throw
белгиленген сап болуп саналат (2)
. Ошентип, биз аны тышкы коддон төмөнкүдөй кармай алабыз:
@A@function* generate() {
let result = yield "2 + 2 = ?"; // Ошибка в этой строке
}
let generator = generate();
let question = generator.next().value;
try {
generator.throw(new Error("Ответ не найден в моей базе данных"));
} catch(e) {
alert(e); // покажет ошибку
}@A@
Эгерде ката ошол жерден кармалбаса, анда андан ары - адаттагыдай эле, ал түшүп калат жана эгер кармалбаса, сценарий "жыгылып" калат.
Бардыгы
- Генераторлор генератор функцияларын колдонуу менен түзүлөт
function* f(…) {…}
. - Генераторлордун ичинде жана алардын ичинде гана оператор бар
yield
. - Тышкы код жана генератор аралык натыйжаларды аркылуу алмашат
next/yield
.
Заманбап JavaScriptте генераторлор сейрек колдонулат. Бирок кээде алар пайдалуу, анткени функциянын иштөө убагында чалуучу менен байланышуу мүмкүнчүлүгү абдан уникалдуу. Жана, албетте, кайталануучу объекттерди түзүү.
Ошондой эле, кийинки бөлүмдө биз асинхрондук генераторлорду изилдейбиз, алар асинхрондуу түрдө түзүлгөн маалыматтардын агымын (мисалы, тармактан беттелген) циклде окуу үчүн колдонулат for await ... of
.
Веб программалоодо биз көбүнчө маалымат агымдары менен иштейбиз, ошондуктан бул дагы бир маанилүү колдонуу учуру.
Tasks
Кокус маалыматтар керек болгон көптөгөн аймактар бар.
Алардын бири сыноо болуп саналат. Бизге туш келди маалыматтар керек болушу мүмкүн: текст, сандар ж.б.
JavaScript'те биз колдоно алабыз Math.random()
. Бирок бир нерсе туура эмес болуп кетсе, биз ошол эле маалыматтарды колдонуу менен тестти кайра иштетишибиз керек болот.
Бул үчүн, "урук псевдо-кокус генераторлор" деп аталган колдонулат. Алар биринчи маани катары "тукумду" алышат, андан кийин формуланы колдонуп кийинкисин чыгарышат. Ошентип, бир эле урук бирдей ырааттуулукту берет, демек, бүт агым оңой кайталанат. Биз ырааттуулукту кайра чыгаруу үчүн урукту жаттап алышыбыз керек.
Аздыр-көптүр бирдей бөлүштүрүлгөн маанилерди жараткан формуланын мисалы:
next = previous * 16807 % 2147483647
Биз үрөн катары колдонсок 1
, анда баалуулуктар болот:
16807
282475249
1622650073
- …жана башка…
Максаты – көрсөтүлгөн формула менен генераторду pseudoRandom(seed)
кабыл алган жана түзгөн генератор функциясын түзүү.seed
Колдонуу мисалы:
@A@let generator = pseudoRandom(1);
alert(generator.next().value); // 16807
alert(generator.next().value); // 282475249
alert(generator.next().value); // 1622650073@A@