Вопросы на собеседовании фронтенд-разработчика. Генераторы в JavaScript
Определение
Генератор — особая разновидность функций, которые могут останавливаться и запускаться в определённой точке кода (yield).
Такой тип функций не запускается сам по себе. Для управления создается объект-итератор (экземпляр генератора), который вызывает метод next() для перехода к следующему yield или в конец функции:
function* someGenerator(a, b) {
return a + b;
}
const iterator = someGenerator(2, 3);
const result = iterator.next();
console.log(result.value) // 5Запуск и остановка генератора
Вызов функции-генератора ещё не запускает его работу. В момент вызова происходит конструирование итератора (const iterator = someGenerator()), который будет управлять генератором.
Запуск генератора произойдёт на первом вызове next() итератора. Далее последует переход к команде yield и только после этого первый вызов next() завершится. В этот момент генератор находится в приостановленном состоянии. Со следующим вызовом next() будет осуществлён переход к следующей команде yield или в конец генератора.
Значение каждого шага генератора находятся в свойстве value объекта, который возвращает вызов next().
Передача значений
При вызове генератор принимает параметры как и обычная функция, но существует и другой способ передачи значений внутрь генератора.
После запуска генератора (первый вызов next()) происходит переход к yield. Совершая второй вызов next() мы можем передать в генератор значение. Это значения будет результатом выполнения yield:
function* someGenerator(message) {
const time = yield
return `${message} <<< ${time}` ;
}
const iterator = someGenerator('текст');
iterator.next(); // запуск генератора и его остановка на yield
const result = iterator.next('15:45'); // результат для yield
console.log(result.value) // текст <<< 15:45Генератор способен возвращать значения наружу. Это выглядит как обычный return функции, только возвращаемое значение попадает в свойство value. Как указывалось выше, это свойство находится в результате вызова next().
Если после yield указать какое-либо значение и вызвать next(), который запускает переход именно к этому yield, то мы сразу получим к нему доступ через свойство value. Если ничего не указывать, то в value будет undefined.
Как итог, во время работы генератора образуется двусторонняя система передачи значений между yield и next().
Цикл for..of
Для перебора итератора можно использовать цикл for..of. Он позволяет не вызывать каждый раз метод next() итератора для получения значения.
function* someGenerator() {
yield 1;
yield 2;
yield 3;
}
const iterator = someGenerator();
for(const value of iterator) {
console.log(value);
}
// 1
// 2
// 3Команда return(..) и try..finally
Завершить итератор можно принудительно. Для этого нужно вызвать у итератора метод return(..), в который можно передать значение для "финального" value.
const iterator = someGenerator();
const result = iterator.return('the end');
console.log(result) // { value: 'the end', done: true }Если внутри генератора есть конструкция try..finally, то она будет выполняться даже при внешнем завершении.
Передача управления
У функции-генератора существует возможность передачи управления (делегирования). Это реализуется с помощью команды yield *. Передавать управление можно не только другой функции-генератор, но и любому итерируемому объекту.
const someArr = ['-_-', '!'];
function* someGenerator() {
yield 'start';
yield *someArr;
yield 'finish';
}
for(const value of someGenerator()) {
console.log(value);
}
// start
// -_-
// !
// finishКогда итератор видит команду yield *anotherGenerator(), то anotherGenerator() создаст свой итератор, который после завершения вернёт управление обратно. Важно, что команда yield * передаёт управление именно итерациями, а не генератором.
function* mainGenerator() {
yield 'start';
yield *anotherGenerator();
yield 'finish';
}
function* anotherGenerator() {
yield 'some action 1';
yield 'some action 2';
}
for(const value of mainGenerator()) {
console.log(value);
}
// start
// some action 1
// some action 2
// finishПередача управления упрощает чтение кода и его тестирование.