Як працюють генератори у JavaScript

7 хв. читання

Перш ніж перейдемо до генераторів, згадаймо деякі основні принципи роботи функцій:

  • У JavaScript функції являють собою набір виразів, які виконують певне завдання та повертають значення при завершенні;
  • Якщо ви викликаєте функцію знову і знову, вона кожен раз виконуватиме вирази;
  • Стріли, випущені з лука, неможливо зупинити — вони або влучать, або схиблять. Те ж саме і з функціями: якщо функція викликана, вона виконуватиметься, повертатиме значення і зупиниться лише виконавши усі вирази у тілі, або при виникненні помилки.

Для розуміння генераторів важливо засвоїти згадані принципи.

Генератори

Генератор — особливий вид функції, яка може зупинити своє виконання у певний момент та розпочати з нього ж при наступному запуску. Такий принцип роботи отримуємо внаслідок поєднання функцій та ітераторів. На перший погляд виглядає заплутано, але під кінець статті ви розумітимете про що йде мова.

Уявіть що ви граєте у гру і раптово мама кличе вас допомогти. Ви ставите гру на паузу, допомагаєте мамі, а потім відновлюєте гру. Те ж саме з генераторами.

Ітератор — об'єкт, що визначає послідовність та потенційно значення, що повертається на кожній ітерації.

Ітератори — досить об'ємна тема і виходить за межі цієї статті.

Базовий синтаксис

Генератори записуються як функція із символом зірочки *:

function* name(arguments) {
   statements
}

Де:

  • name — назва функції;
  • arguments — її аргументи;
  • statements — тіло функції.

Return

Функція може повертати різноманітні значення: як примітивних типів, так і об'єкти та безпосередньо функції. Функція-генератор повертає спеціальний об'єкт – генератор (не зовсім вірно). Він виглядає приблизно так:

{ 
  value: value,
  done: true|false
}

Як бачимо, об'єкт містить дві властивості: value та done. value зберігає значення, яке буде утворено. done складається з boolean(true|false). Так генератор знатиме, що при наступному виклику next() утвориться нове значення або undefined.

Щоб зрозуміти логіку роботи генератора, розглянемо приклад:

Як працюють генератори у JavaScript
function* generator(e) {
  yield e + 10;
  yield e + 25;
  yield e + 33;
}
var generate = generator(27);
console.log(generate.next().value); // 37
console.log(generate.next().value); // 52
console.log(generate.next().value); // 60
console.log(generate.next().value); // undefined

Проаналізуємо наведений код рядок за рядком:

  • Рядок 1-5: Визначено генератор з тією ж назвою, що й у аргумента — e. У тілі він містить набір виразів з ключовим словом yield та деякими операторами після.
  • Рядок 6: У рядку 6 генератор присвоюється змінній generate.
  • Рядок 8-11: Виклики console.log, в які передається генератор, що викликає ланцюжком next та властивість value об'єкту генератора.
Як працюють генератори у JavaScript

Всякий раз, коли викликається функція-генератор, вона не починає виконання спочатку, на відміну від звичайних функцій. Замість цього повертається ітератор (саме тому при визначенні ми використовуємо символ * — так JS визначає, що повертається об'єкт ітератора). Коли викликається метод ітератора next(), виконання функції починається та продовжується, поки не зустрінеться перший вираз з ключовим словом yield. На цьому етапі повертається вже згаданий об'єкт генератора. З черговим викликом next() ми поновлюємо виконання функції-генератора, поки не буде досягнуто yield, і така послідовність повторюється, поки існують вирази з yield.

Як працюють генератори у JavaScript

Тепер при наступному виклику next() об'єкт генератора повернеться зі значенням undefined.

Оглянемо іншу функцію-генератор, але вже з оператором return.

Як працюють генератори у JavaScript

Оператор return, як і зі звичайною функцією, завершить виконання генератора. Властивість done прийме значення true, а як value буде встановлено повернене значення. Усі yield, що залишилися, повернуть undefined.

Якщо під час виконання, виникне помилка, то генератор припинить виконання, а yield передасть інший генератор.

Як працюють генератори у JavaScript

Зверніть увагу, * після yield необхідна, щоб повідомити JS, що далі йде генератор. yield* запускає генератор generator2, тому ми можемо звернутися до його значень, викликавши generate.next(). Перше значення отримано з генератора generator, а останні два значення згенеровано generator2, але все одно отримано після виклику generator.

Переваги

Відкладене завантаження

Відкладене завантаження, по суті, оптимальне, лише коли в ньому існує потреба. Як ми побачимо з наступного прикладу, такого ефекту можна досягти з генераторами. Можна надавати значення лише коли вони необхідні, і не всі одночасно.

У прикладі нескінченно генеруються випадкові числа. Тобто можна викликати next() необмежену кількість разів та отримати лише необхідні значення, а не усі згенеровані.

Як працюють генератори у JavaScript
function * randomize() {
  while (true) {
    let random = Math.floor(Math.random()*1000);
    yield random;
  }
}
var random= randomize();

console.log(random.next().value)

Ефективне використання пам'яті

Як можна здогадатися з попереднього прикладу, генератори дуже оптимальні для пам'яті. Оскільки ми отримуємо значення лише за потребою, зменшується обсяг необхідної пам'яті для їх зберігання.

Підводні камені

Генератори досить корисні, але тут не обійтися без недоліків:

  • Генератори не забезпечують довільного доступу, як масиви чи інші структури даних. Оскільки значення генеруються за викликом по одному, ми не можемо отримати доступ до випадкових елементів.
  • Генератори забезпечують одноразовий доступ. Ви не можете пробігатися значеннями генератора знову і знову. Після того, як всі значення вичерпані, ми повинні створити новий екземпляр генератора, щоб пробігтися ним знову.

Навіщо нам генератори?

Генератори мають широкий спектр застосувань у JavaScript. Оглянемо декілька з них.

Реалізація Ітераторів

Ітератор — об'єкт, що дозволяє переміщатися по контейнеру.

Виведемо у консоль усі слова з рядка з використанням ітераторів. Рядки також являють собою ітератори.

Як працюють генератори у JavaScript
const string = 'abcde';
const iterator = string[Symbol.iterator]();
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)

Те ж саме з використанням генераторів:

Як працюють генератори у JavaScript
function * iterator() {
  yield 'a';
  yield 'b';
  yield 'c';
  yield 'd';
  yield 'e';
}
for (let x of iterator()) {
  console.log(x);
}

Порівнявши два способи реалізації, бачимо, що з генераторами код більш організованний. Варто звернути увагу на такі моменти:

  • Немає викликів next();
  • Немає виклику [Symbol.iterator]();
  • У деяких випадках, при використанні ітераторів, необхідно встановити значення object.done, що повертатиме true/false.

Async-Await ~ Проміси+Генератори

Якщо ви не знайомі з Async-Await, ознайомтеся з матеріалом, а більш детальна інформація про проміси тут.

Грубо кажучи, Async-Await — просто реалізація генераторів, що використовуються з промісами.

  • Async-Await
async function async-await(){
  let a = await(task1);
  console.log(a);
  let b = await(task2);
  console.log(b);
  let c = await(task3);
  console.log(c);
}
  • Проміси+Генератори
function * generator-promise()
{
  let a = yield Promise1();
  console.log(a);
  let b = yield Promise1();
  console.log(b);
  let c = yield Promise1();
  console.log(c);
}

Як можна побачити, обидва способи дають один і той же результат і майже однаковим чином. Усе тому що механізм Async-Await частково заснований на комбінації генераторів та промісів. Async-Await має ще багато можливостей. Тут був наведено приклад для наочного порівняння з генератором.

Нескінченна Структура Даних

Ми можемо створити генератори, з використанням нескінченного циклу while, який завжди буде генерувати значення.

Як працюють генератори у JavaScript
function * randomize() {
  while (true) {
    let random = Math.floor(Math.random()*1000);
    yield random;
	}
}
var random= randomize();
while(true)
  console.log(random.next().value)

У наведеному фрагменті ми створюємо нескінченний генератор, який надаватиме випадкове число при кожному виклику next(). Тобто отримали нескінченний потік випадкових чисел.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація