Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос нашим менторам. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Корутины

В Computer Science под генераторами понимается data producer, то есть сущность в языке, которая только выдает наружу данные, используя yield. При этом существует более общая концепция, которая называется coroutine или сопрограмма. В отличие от генераторов, она может не только генерировать данные, но так же может и потреблять их (data consumer). Самым удивительным в этой истории является то, что генераторы в js, по сути, являются корутинами, а использование их в качестве генераторов – это всего лишь один из возможных вариантов.

Сопрограмма — компонент программы, обобщающий понятие функции, который дополнительно поддерживает множество входных точек (а не одну, как функция), остановку и продолжение выполнения с сохранением определенного положения.

const gen = function* () {
  const a = yield 10;
  const b = yield;
  return a + b;
};

const coroutine = gen();
const result = coroutine.next(); // { value: 10, done: false }
coroutine.next(result.value + 1); // const a = 11
console.log(coroutine.next(15)); // const b = 15
// => { value: 26, done: true }

https://repl.it/@hexlet/js-sync-coroutines-gen

Главное, на что нужно обратить внимание, это появление выражения yield справа от знака равно: const a = yield 10.

Попробуем по шагам выполнить этот код:

  1. Создание корутины const coroutine = gen();
  2. Вызов next(). Первый вызов приводит к тому, что наружу возвращается { value: 10, done: false }, так как внутри мы оказываемся в точке yield 10.
  3. Вызов next(result.value + 1). Выражение result.value + 1 равно 11, поэтому в итоге происходит вызов next(11). Внутри корутины мы находимся в этой позиции const a = yield. Аргумент, переданный в next, оказывается записанным в константу a внутри корутины и код продолжает выполнятся до следующего вызова yield, на котором корутина останавливается, и управление возвращается наружу.
  4. Дальнейший вызов next(15) приводит к тому, что константа b становится равна 15, а наружу возвращается { value: 26, done: true }.

Если обобщить, то yield <что-то> производит данные наружу, const a = yield потребляет данные, а const a = yield <что-то> производит и потребляет в два шага.

Теперь, используя немного магии, мы можем создать обертку над генераторами для работы с асинхронным кодом.

co(function* () {
  const a = yield Promise.resolve(1);
  const b = yield Promise.resolve(2);
  const c = yield Promise.resolve(3);

  console.log([a, b, c]); // => [1, 2, 3]
});

Идея в том, что функция co автоматически итерирует по генератору, извлекая значение из промисов и передавая их дальше в next по цепочке. В целом, на этом можно было бы и остановиться, но для полной имитации синхронной работы хотелось бы поддержки со стороны try/catch. И генераторы дают возможность трансформировать ошибки в исключения.

co(function* () {
  const a = yield Promise.resolve(1);
  try {
    const b = yield Promise.reject(new Error('Boom'));
  } catch (e) {
    console.log(e.message); // => 'Boom'
  }
});

Чтобы такой код заработал, необходимо в функции co отслеживать состояние rejected и использовать метод throw, который есть у нашего генератора. Ниже пример того, как это можно было бы сделать (без промисов):

const gen = function* () {
  try {
    const a = yield;
    yield new Error('Boom');
  } catch (e) {
    console.log(e.message);
  }
  console.log('after Boom');
};

const coroutine = gen();
coroutine.next();
const result = coroutine.next();
coroutine.throw(result.value); // => { value: undefined, done: true }

// Boom
// After Boom

Метод throw() возобновляет выполнение тела генератора кидая внутри исключение и возвращает объект со свойствами done и value.


Дополнительные материалы

  1. Генераторы (YDNJS)

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят менторы из команды Хекслета или другие студенты.

Для полного доступа к курсу нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, даст возможность обращаться за помощью к менторам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».