W3docs

Функции-генераторы в JavaScript

Генераторы JavaScript: синтаксис function*, yield и yield*, передача значений через next(), return() и throw(), ленивые бесконечные последовательности.

Генератор — это особый вид функции, которая может приостановить своё выполнение в любой момент, вернуть значение вызывающему коду, а затем продолжить работу ровно с того места, где остановилась. Вместо того чтобы вычислять всё сразу и возвращать единственный результат, генератор производит последовательность значений лениво — по одному, только по запросу.

Эта идея делает генераторы самым элегантным способом в JavaScript создавать пользовательские итерируемые объекты, моделировать бесконечные последовательности без утечек памяти и выполнять длительную логику по требованию. В этом руководстве рассматриваются синтаксис function*, yield и yield*, двусторонняя коммуникация с помощью next(), досрочное завершение и практические паттерны использования генераторов.

Эта страница опирается на материал об итерируемых объектах и итераторах — если протокол итераторов вам ещё незнаком, сначала прочитайте тот раздел.

Понимание функций-генераторов

Основы функций-генераторов

Функция-генератор объявляется как обычная функция, но после ключевого слова function ставится звёздочка: function*. Именно звёздочка отличает генератор от обычной функции.

Вызов функции-генератора не запускает её тело. Вместо этого возвращается объект-генератор — итератор, которым управляют вручную. Каждый вызов метода next() выполняет тело до следующего yield, затем останавливается и возвращает { value, done }:

javascript— editable

Первый next() выполняется до первого yield 'Hello' и останавливается там. Второй продолжает выполнение с этой точки и доходит до yield 'World'. Третий возобновляет работу, не находит больше операторов yield, достигает конца функции и возвращает done: true. Последующие вызовы next() продолжат возвращать { value: undefined, done: true }.

Поскольку объект-генератор также является итерируемым, его можно обойти с помощью for...of, оператора расширения или деструктуризации — все они автоматически останавливаются, когда done становится true:

javascript— editable
Примечание

Объект-генератор можно обойти только один раз. После исчерпания for...of не выдаст ничего. Чтобы получить свежий генератор, вызовите функцию-генератор заново.

Возвращаемое значение генератора

Оператор return внутри генератора завершает итерацию и передаёт финальное value вместе с done: true. Обратите внимание, что for...of игнорирует это возвращаемое значение — он видит только значения, переданные через yield:

javascript— editable

Управление потоком выполнения с помощью return() и throw()

Помимо next(), объект-генератор предоставляет ещё два метода, которые позволяют вызывающему управлять выполнением извне:

  • generator.return(value) принудительно завершает генератор немедленно, выполняя все блоки finally по пути, и возвращает { value, done: true }.
  • generator.throw(error) внедряет исключение в точке текущего yield, чтобы генератор мог перехватить его через try...catch — или пробросить дальше, если не перехватывает.
javascript— editable

Блок finally делает генераторы удобным местом для освобождения ресурсов (закрытие файла, отключение потока) даже при досрочном прерывании итерации.

Продвинутые паттерны генераторов

Делегирование с помощью yield*

Когда один генератор должен воспроизвести значения другого генератора или любого итерируемого объекта, используйте yield* (читается «yield-delegate») вместо написания цикла вручную. Он прозрачно пересылает каждое значение из делегированного источника:

javascript— editable

Важная деталь: значение выражения yield* равно значению return внутреннего генератора, что позволяет компоновать генераторы и возвращать результат во внешний:

javascript— editable

Передача значений в генераторы

Взаимодействие с генератором работает в обе стороны. Выражение yield не только отправляет значение наружу через next(), но и вычисляется в то, что вы передаёте внутрь при следующем вызове next(value). Это превращает генератор в небольшую интерактивную корутину:

javascript— editable

Первый next() выполняется до первого yield и возвращает вопрос; его аргумент отбрасывается, поскольку ни один yield ещё не находится в режиме ожидания. Второй next('Alice') возобновляет приостановленный yield, так что выражение принимает значение 'Alice' и присваивается переменной name.

Практическое применение генераторов JavaScript

Создание пользовательских итерируемых объектов

Самое распространённое практическое применение генераторов — сделать собственные объекты итерируемыми. Определите [Symbol.iterator] как метод-генератор, и на объекте заработают for...of, оператор расширения и деструктуризация. Это требует значительно меньше шаблонного кода, чем написание объекта-итератора с ручной реализацией next() (о том, что такое Symbol.iterator, читайте в разделе Тип Symbol):

javascript— editable

Ленивые и бесконечные последовательности

Поскольку генератор вычисляет каждое значение только по запросу, он может описывать концептуально бесконечные последовательности, не расходуя память. Классический пример — счётчик, однако тот же подход применяется для диапазонов, генераторов идентификаторов или потоков Фибоначчи. Для работы с такими последовательностями используют вспомогательную функцию «take», которая останавливается после нужного количества элементов:

javascript— editable

Параметризованный генератор диапазона — удобный повторно используемый вариант той же идеи:

javascript— editable

Управление асинхронными операциями

Исторически генераторы применялись для борьбы с «адом колбэков»: каждый yield приостанавливался на Promise, а внешний «раннер» возобновлял генератор после разрешения этого Promise. Пример ниже демонстрирует ручную механику, чтобы вы понимали, что происходит под капотом:

javascript— editable
Примечание

Это упрощённый ручной раннер. В современном коде вы написали бы это с помощью async/await, который язык построил именно на основе данного паттерна генератор + Promise. Для асинхронной итерации (потоковая передача данных по частям) используйте асинхронные генераторы с async function* и for await...of.

Генераторы и обычные функции

АспектОбычная функцияФункция-генератор
Объявлениеfunction fn()function* fn()
Вызоввыполняет тело до концавозвращает объект-генератор, ничего не запускает
Возвращаетодно значениепоток значений через yield
Может приостановиться?нетда, на каждом yield
Итерируема?нетда (работает с for...of, оператором расширения)

Используйте генератор, когда нужны значения, производимые лениво или по требованию, когда объект должен быть итерируемым, или когда последовательность может быть бесконечной. Для простого одноразового вычисления обычная функция проще и быстрее.

Заключение

Генераторы дают JavaScript чистый способ производить последовательности лениво, приостанавливать и возобновлять логику, а также делать любые объекты совместимыми с циклами вроде for...of. Освойте yield для выдачи значений, yield* для делегирования другим итерируемым объектам и next()/return()/throw() для управления потоком выполнения — и у вас окажется инструмент, лежащий в основе всего: от пользовательских итерируемых объектов до синтаксиса async/await, построенного на его базе.

Практика

Практика
Какова роль символа '*' перед ключевым словом function в генераторах JavaScript?
Какова роль символа '*' перед ключевым словом function в генераторах JavaScript?
Was this page helpful?