Генераторы - это новый вид функций в JS. Они отличаются от обычных тем что могут приостанавливать своё выполнение, возвращать промежуточный результат и далее возобновлять своё выполнение позже, в промежуточный момент времени.
Для того чтобы сделать функцию генератор - нужно проделать следующее
function* generate() {
console.log('Start');
yield;
console.log('Finish');
}
let iterator = generate();
iterator.next();
iterator.next();
// Start
// Finish
Дополнительные способы определить генератор
let generator = function*(){
}
let obj = {
*generator(){}
}
class SomeClass {
*generator(){}
}
Генераторы работают как фабрика итераторов.
Когда мы вызываем функцию генератор - мы получаем объект итератор, с помощью которого мы можем приостанавливать и возобновлять процесс выполнения функции. В свою очередь слово yield - производит и отдаёт информацию, т.е. объект со свойствами IteratorResult{done, value}, но при этом также уступает или отдаёт контроль над функцией.
Кроме приостановки ключевое слово yield - также возвращает промежуточный вариант вычислений и передаёт данные в функцию.
function* generate() {
console.log('Start');
yield 1;
yield 2;
yield 3;
console.log('Finish');
}
let iterator = generate();
console.log( iterator.next() ); // первый вызов - запускает генератор, в него нельзя отправить значение
console.log( iterator.next() );
console.log( iterator.next() );
console.log( iterator.next() );
// Start
// Object { value: 1, done: false }
// Object { value: 2, done: false }
// Object { value: 3, done: false }
// Finish
// Object { value: undefined, done: true }
Создание собственных итераторов может быть весьма полезным, но при их создании требуется уделять большое внимание поддержке внутреннего состояния. Генераторы предоставляют альтернативу - они позволяюбт определить алгоритм перебора написав функцию, которая умеет сама поддерживать собственное состояние.
В качестве более пркатического пример создадим функцию range, с помощью которой будем получать ряд чисел. А то с какого числа начинать и заканчивать - будет указано в аргументах.
function * range(start, end) {
let current = start;
while (current <= end) {
yield current++; // возвращаем значение с помощью yield
}
}
for (let num of range(1, 10)) {// перебираем значения с помощью итератора объекта
console.log(num);
}
Делегирование генератора
Делегирование генератора - это метод когда элементы массива отдаются по одному, так если бы перед каждыйм элементом стояло бы ключевое слово yield.
function * generator(){
yield 42;
yield* [1, 2, 3]; // Делегирование генератора
yield 43;
}
let iterator = generator();
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);
// 42
// 1
// 2
// 3
// 43
// undefined
Также при помощи синтаксиса yield * - в теле одного генератора можно вызвать другой генератор.
function * generateArray(){
yield * [1, 2, 5];
}
function * generator(){
yield 42;
yield * generateArray();
yield 44;
}
let iterator = generator();
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);
// 42
// 1
// 2
// 5
// 44
// undefined
Функция генератор возвращает объект соответсвующий интерфейску итератора. Помимо метода next() у возвращаемого объекта есть ещё 2 метода, которых нет у итераторов. С помощью этих методов можем досрочно остановить генератор.
iterator{
next(),
return(),
throw()
}
Отличие return() от throw() в том что return() - просто останавливает выполнение, а throw() - генерирует ошибку которую модно поймать конструкцией try...catch.
function * generator(){
try{
yield 1;
yield 2;
yield 3;
}
catch(e){
console.log(e)
}
}
let iterator = generator();
console.log(iterator.next());
console.log(iterator.throw(new Error('Custom Generated Error')));
console.log(iterator.next());
// Object { value: 1, done: false }
// Error: Custom Generated Error
// Object { value: undefined, done: true }
// Object { value: undefined, done: true }
Генераторы используются для написания асинхронного кода. Причём асинхронности можно добиться без функций обратного вызова и обещаний. Если обещания совместить с генераторами - то пулучится идеальная смесь которая в следующей версии JS будет носить название async.