Если вы были разработчиком JavaScript в течение последних двух-пяти лет, вам наверняка приходилось сталкиваться с сообщениями о генераторах и итераторах. Хотя Генераторы и Итераторы по своей сути связаны, Генераторы кажутся немного более устрашающими, чем другие.

Итераторы - это реализация итерируемых объектов, таких как карты, массивы и строки, которая позволяет нам выполнять итерацию по ним с помощью next (). У них есть множество вариантов использования операторов Generators, Observables и Spread.

Я рекомендую следующую ссылку для тех из вас, кто плохо знаком с итераторами: Руководство по итераторам.

Чтобы проверить, соответствует ли ваш объект итеративному протоколу, проверьте с помощью встроенного Symbol.iterator:

new Map([[1, 2]])[Symbol.iterator]() // MapIterator {1 => 2}
“hi”[Symbol.iterator]() // StringIterator {}
[‘1’][Symbol.iterator]() // Array Iterator {}
new Set([1, 2])[Symbol.iterator]() // SetIterator {1, 2}

Генераторы, представленные как часть ES6, не претерпели никаких изменений в следующих выпусках JavaScript, и они останутся здесь надолго. Я имею ввиду очень долго! Так что от этого не убежать. Хотя у ES7 и ES8 есть некоторые новые обновления, они не имеют такого же масштаба изменений, как ES6 по сравнению с ES5, который поднял JavaScript на новый уровень, так сказать.

Я уверен, что к концу этой публикации вы получите четкое представление о том, как работают генераторы функций. Если вы профессионал, пожалуйста, помогите мне улучшить контент, добавив свои комментарии в ответы. На всякий случай, если вам трудно следовать коду, я также добавил пояснения к большей части кода, чтобы помочь вам лучше понять.

Вступление

Функции в JavaScript, как мы все знаем, «выполняются до возврата / завершения». Функции генератора, напротив, «работать до yield / return / end». В отличие от обычных функций Функции генератора, однажды вызванные, возвращает объект генератора, который содержит весь Итерируемый генератор, можно повторить с помощью метода next () или цикла for… of.

Каждый вызов next () в генераторе выполняет каждую строку кода до следующего встретившегося yield и временно приостанавливает его выполнение.

Синтаксически они обозначаются *, либо функция * X, либо функция * X, - оба означают одно и то же. вещь.

После создания вызов функции генератора возвращает объект генератора. Этот объект-генератор необходимо присвоить переменной, чтобы отслеживать последующие методы next (), вызываемые для него самого. Если генератор не назначен переменной, он всегда будет давать результат только до первого выражения yield на каждом следующем ().

Функции генератора обычно создаются с использованием выражений yield. Каждый yield внутри функции генератора является точкой остановки перед началом следующего цикла выполнения. Каждый цикл выполнения запускается с помощью метода next () в генераторе.

При каждом вызове next () выражение yield возвращает свое значение в виде объекта, содержащего следующие параметры.

{ value: 10, done: false } // assuming that 10 is the value of yield

  • Значение - это все, что написано справа от ключевого слова yield, это может быть вызов функции, объект или что-то еще. Для пустых урожаев это значение равно undefined.
  • Готово - указывает состояние генератора, может ли он выполняться дальше или нет. Когда done возвращает true, это означает, что функция завершила свой запуск.

(Если вы чувствуете, что это немного выше вашей головы, вы получите больше ясности, когда увидите пример ниже…)

Примечание. В приведенном выше примере функция генератора, к которой осуществляется прямой доступ без оболочки, всегда выполняется только до первого yield. Следовательно, по определению вам необходимо назначить Генератор переменной, чтобы правильно ее перебирать.

Жизненный цикл функции генератора

Прежде чем мы продолжим, давайте быстро взглянем на блок-схему жизненного цикла функции генератора:

Каждый раз, когда обнаруживается yield, функция генератора возвращает объект, содержащий значение обнаруженного yield и Готово. Точно так же, когда встречается return, мы получаем возвращаемое значение, а также статус done как true. Всякий раз, когда статус done возвращается как true, это по сути означает, что функция генератора завершила свой запуск, и дальнейшее выполнение невозможно.

Все после первого return игнорируется, включая другие выражения yield.

Прочтите дальше, чтобы лучше понять блок-схему.

Присвоение доходности переменной

В предыдущем примере кода мы увидели введение в создание базового генератора с yield. И получил ожидаемый результат. Теперь предположим, что мы присваиваем все выражение yield переменной в приведенном ниже коде.

Каков результат всего выражения yield, переданного переменной? Ничего или не определено…

Почему ? Начиная со второго next (), предыдущий yield заменяется аргументами, переданными в следующую функцию. Поскольку в методе next мы ничего не передаем, предполагается, что все выражение previous-yield целиком не определено.

Имея это в виду, давайте перейдем к следующему разделу, чтобы узнать больше о передаче аргумента методу next ().

Передача аргументов методу next ()

Обращаясь к приведенной выше блок-схеме, давайте поговорим о передаче аргументов в следующую функцию. Это одна из самых сложных частей всей реализации генератора.

Давайте рассмотрим следующий фрагмент кода, в котором доходность присваивается переменной, но на этот раз мы передаем значение в методе next ().

Давайте посмотрим на приведенный ниже код в консоли. И сразу после этого объяснение.

Объяснение:

  1. Когда мы вызываем first next (20), печатается каждая строка кода до первого результата. Поскольку у нас нет предыдущего выражения yield, это значение 20 отбрасывается. На выходе мы получаем значение доходности i * 10, которое здесь равно 100. Также состояние выполнения останавливается с первым yield, а const j еще не установлено.
  2. Второй вызов next (10) заменяет все первое выражение yield целиком на 10, представьте себе yield (i * 10) = 10, , который устанавливает значение const j равным 50 перед возвратом второго значения yield. Значение доходности здесь составляет 2 * 50/4 = 25.
  3. Третий next (5) заменяет весь второй yield на 5, доводя значение k до 5. И далее продолжает выполнять оператор return и return (x + y + z) = ›(10 + 50 + 5) = 65 в качестве окончательного значения доходности вместе со значением done true.

Это может показаться немного ошеломляющим для начинающих читателей, но потратьте добрых 5 минут, чтобы перечитывать его снова и снова, чтобы понять полностью.

Передача доходности как аргумента функции

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

Объяснение

  1. Первый next () возвращает неопределенное значение, потому что выражение yield не имеет значения.
  2. Второй метод next () возвращает значение, которое было передано. И подготавливает аргумент для вызова функции.
  3. Третий метод next () вызывает функцию с аргументом undefined. Как упоминалось выше, метод next (), вызываемый без каких-либо аргументов, по существу означает, что все предыдущее выражение yield не определено. Следовательно, это печатает undefined и завершает выполнение.

Доходность с вызовом функции

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

Приведенный выше код возвращает объект возврата функции в качестве значения yield. И заканчивает выполнение, устанавливая undefined для const пользователя.

Доходность с обещаниями

Yield with promises следует тому же подходу, что и вызов функции выше, вместо того, чтобы возвращать значение из функции, он возвращает обещание, которое может быть дополнительно оценено на успех или неудачу. Давайте посмотрим на приведенный ниже код, чтобы понять, как он работает.

ApiCall возвращает обещания в виде значения yield, при разрешении через 2 секунды выводит нужное нам значение.

Урожай*

До сих пор мы рассматривали варианты использования выражения yield, теперь мы рассмотрим другое выражение под названием yield *. Yield * при использовании внутри функции генератора делегирует другую функцию генератора. Проще говоря, он синхронно завершает функцию генератора в своем выражении, прежде чем перейти к следующей строке.

Давайте посмотрим на код и пояснения ниже, чтобы лучше понять. Этот код взят из веб-документации MDN.

Объяснение

  1. Первый вызов next () возвращает значение 1.
  2. Однако второй вызов next () - это yield * выражение, что по сути означает, что мы собираемся завершить другую функцию генератора, указанную в yield * expression перед продолжением текущей функции генератора.
  3. На ваш взгляд, вы можете предположить, что приведенный выше код заменен следующим образом.
function* g2() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
}

Это продолжит работу генератора. Однако есть одна особенность yield *, о которой следует помнить при использовании return в следующий раздел.

Доходность * с возвратом

Доходность * с возвратом ведет себя немного иначе, чем нормальная доходность *. Когда yield * используется с оператором return, он оценивает это значение, что означает, что вся функция yield * () становится равной значению, возвращаемому из связанной функции генератора.

Давайте посмотрим на код и пояснения ниже, чтобы лучше понять его.

Объяснение

  1. В первом next () мы переходим прямо к yield 1 и возвращаем его значение.
  2. Второй next () дает 2.
  3. Третий метод next () возвращает 'foo' и продолжает возвращать 'конец', присваивая 'foo' результату const в пути.
  4. Последний next () завершает выполнение.

Доходность * со встроенным итерируемым объектом

Следует упомянуть еще одно интересное свойство yield *, аналогичное возвращаемому значению: yield * также может выполнять итерацию по повторяемым объектам, таким как Array, String и Map.

Давайте посмотрим, как это работает в режиме реального времени.

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

Лучшие практики

Вдобавок ко всему, каждый итератор / генератор может повторяться в цикле for… of. Подобно нашему методу next (), который вызывается явно, цикл for… of внутренне переходит к следующей итерации на основе ключевого слова yield. И он выполняет итерацию только до последнего yield и не обрабатывает операторы возврата, как метод next ().

Вы можете проверить то же самое в приведенном ниже коде.

Окончательное возвращаемое значение не печатается, потому что цикл for… of повторяется только до последнего yield. Таким образом, рекомендуется избегать операторов return внутри функции генератора, поскольку это может повлиять на возможность повторного использования функции при повторении по for… of.

Заключение

Я надеюсь, что это охватывает основные варианты использования функций генератора, и я искренне надеюсь, что это дало лучшее понимание того, как генераторы работают в JavaScript ES6 и выше. Если вам нравится мой контент, оставьте 1, 2, 3 или даже 50 аплодисментов :).

Следуйте за мной в моей учетной записи GitHub, чтобы увидеть больше проектов JavaScript и Full-Stack: