Пользователь
0,0
рейтинг
11 февраля в 12:18

Разработка → Генераторы в ES6 и асинхронный код по-новому из песочницы

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

Что же такое генераторы и как они работают?


Генераторы — это функции которые можно запустить, приостановить и возобновить на различных стадиях выполнения. По сути дела, эта специальная функция возвращает итератор. Функции генераторы обозначаются звездочкой после ключевого слова function, а вся магия спрятана в использовании специального ключевого слова yield. Оно определяет, что итератор должен вернуть после очередного вызова метода next().

И сразу же пример простого генератора в ES6:

function* myGenerator() {
    yield ‘first’;
    let input = yield ‘second’;
    yield input;
}

// Получаем объект генератора
let iterator = myGenerator();

// Запускаем генератор, доходим до первого yield
console.log(iterator.next()): // { value: ‘first’, done: false }

// Возобновляем(не передаем никакого значения), доходим до второго yield
console.log(iterator.next()); // { value: ‘second’, done: false }

// Возобновляем (передаем значение) доходим до последнего yield
console.log(iterator.next(‘third’)); // { value: ‘third’, done: false }

// Заканчивается работа (yield больше нет)
console.log(iterator.next()); // { value: undefined, done: true }

Итак, что же здесь происходит?

  • Мы объявляем функцию генератор, используя специальный синтаксис function* myGenerator() {}.
  • Первый вызов этой функции возвращает объект итератора. У этого объекта есть метод next для возобновления функции генератора в его текущем состоянии.
  • Функция генератор не начинает своего выполнения до тех пор, пока мы не запустим iterator.next.
  • Каждый раз, при вызове iterator.next, функция возобновляет свое выполнение с места последней паузы и выполняет весь код, пока не «наткнется» на следующий yield и опять станет на паузу.
  • Вызов iterator.next возвращает объект, содержащий значение, которые было передано в yield и флаг, который обозначает закончила функция свое выполнение или нет.

Генераторы в роли итераторов


В начале статьи было упомянуто, что зачастую генераторы используются как простой механизм создания итераторов. Все это можно сделать используя синтаксис for of:

Например:

function* myGenerator() {
   yield ‘first’;
   yield ‘second’;
   yield ‘third’;
}
for (var v of myGenerator()) {
   console.log(v);
}

Или например:

function* myGenerator(start, stop) {
   for (var i = start; i < stop; i++)
      yield i;
}
for (var v of myGenerator()) {
 console.log(v);
}

Ладно, итераторы это здорово, но давайте посмотрим какие вещи покруче можно сделать используя генераторы.

Итак, нам нужно залогиниться на какой то бекенд, затем использовать токен аутентификации для того, чтобы извлекать данные из API. Если применить механизм генераторов это будет выглядеть так:

co(function* () {
   var result = yield login(username, password);
   var posts = yield getPosts(result.token);
   return posts;
}).then(value => {
   console.log(value);
}, err => {
   console.error(err);
});

function login(username, password) {
   return fetch(‘/login’, {
     method: ‘post’,
     body: JSON.stringify({
       username: username
       password: password
})
 }).then(response => response.json());
}
function getPosts(token) {
   return fetch(‘/posts’, {
     headers: new Headers({
       ‘X-Security-Token’: token
   })
 }).then(response => response.json());
}

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

Express vs Koa


Эээээ, Koa? Это что вообще такое?

Koa — это фреймворк нового поколения(из подзаголовка официального сайта), который улучшает механизм написания middleware, используя как раз таки генераторы и библиотеку co, и избавляя таким образом ваше приложения от ада из коллбеков.
С философской точки зрения, Koa стремится “исправить и заменить node”, а Express наоборот, “расширить node”(из документации Koa).


Middleware в Koa построены на механизме генераторов и представляют собой “каскад”. Проще говоря, сначала запускается механизм “вниз по течению”(downstream) через все middleware, а после этого “вверх по течению” через все middleware(upstream).

Итак, пример:

var koa = require('koa');
var app = koa();

// x-response-time

app.use(function *(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  this.set('X-Response-Time', ms + 'ms');
});

// logger

app.use(function *(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
});

// response

app.use(function *(){
  this.body = 'Hello World';
});

app.listen(3000);

Этот пример вернет нам 'Hello World', однако сперва:

  1. Запрос пройдет через middleware x-response-time выполнит все, что идет до строки yield next.
  2. Затем yield next остановит текущую функцию и передаст управление другому middleware logger, он также выполнит все до своего yield next.
  3. В логгере yield next передаст управление последнему middleware.
  4. Он в свою очередь сформирует ответ this.body = 'Hello World'.
  5. Больше middleware для передачи downstream у нас нет, так что начнется upstream процесс. Т.е. каждый middleware возобновляет свою работу после yield next, осуществляя таким образом механизм “вверх по течению”.

Круто в общем, что еще сказать. Подробнее про Koa можно посмотреть на официальном сайте и на гитхабе.

Заключение


Генераторы в ES6 представляют собой мощный механизм, который дает возможность писать более чистый и понятный асинхронный код. Вместо того, чтобы использовать ворох коллбеков по всему коду мы можем теперь писать асинхронный код, который с виду похож на синхронный, но на самом деле при помощи генераторов и ключевого слова yield “поджидает” завершения асинхронной операции.
@Good_Karma
карма
6,0
рейтинг 0,0

Похожие публикации

Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (7)

  • 0
    как то поверхностно, вот бы вы разобрали как устроен `co` и как сделано так что когда yiеld-ишь промисы и другие генераторы их значения возвращаются из выражения, и код выглядит синхронным.
    • 0
      Вот в этой статье я разобрал как устроен `co`. Единственный минус — статья на английском.
  • +8
    1. Koa.js, из которой Вы выдрали co.js, с версии 2.* не использует генераторы как прослойку и советуют использовать async await с babel.js.
    2. Не нужно использовать генераторы таким способом, они для другого предназначались.
    • 0
      2. Не нужно использовать генераторы таким способом, они для другого предназначались.
      В случае использования babel-а, действительно, нет резона насиловать генераторы. А в случае чистого кода, это удобная возможность использовать async-await, без наличия оных, пока v8 их внедряет (жаль они не пишут ориентировочных дат). Потом можно всё хоть автозаменой перевести на нативные async-await.

      Лично я стараюсь в разработке использовать только уже реализованные возможности (пусть и под v8 флагом). Потому что кривого debug-а я уже наелся всласть. Что порой бесполезные стрек-трейсы nodejs, что довольно часто глючащий отладчик в web developer tools.

      Кстати говоря, что там babel делает с async-await? Разбирает в switch или юзает генераторы?
      • +1
        Либо через regenerator, где здоровенный switch-case, либо await заменяет на yield и оборачивает функцией, которую указал в настройках. bluebird.coroutine, на пример.
        Второй вариант поддерживается node нативно, так что async-await можно спокойно использовать, если пишешь под node.
  • +1
    TIL не надо объяснять нововведения на примере их использования совместно с библиотеками.
    Хотел почитать про генераторы (потому что до сих пор не понимаю зачем они), прочитал про co и Koa и что там выполнение будет прыгать между функций
    Затем yield next остановит текущую функцию и передаст управление другому middleware logger, он также выполнит все до своего yield next.
    звучит как goto АД.
    • +1
      Почитайте вот это. Генераторы нужны не для чисел Фибоначчи, конечно, как это обычно демонстрируют в примерах. Генераторы это просто такой сахар, для того чтобы итерировать что-нибудь так, как этого хочется вам. К примеру jQuery выборку пропускать через for-of, получая в качестве элемента .eq(index). Насколько фантазии хватит.

      Т.е. генераторы это логическое продолжение всей этой песне-пляски с итерированием. Т.е. for-of, Symbol.iterator, geneator's это всё об одном и том же.

      Так получилось, что в деле генераторы используются именно в качестве реализации сахара для async, await. Т.е. совсем для других целей. В этом просто нужды оказалось больше, чем в полноценных итераторах. Забавно вышло.
      Лично я надеюсь на скорейшую нативную имплементацию async-ов.

      А ещё генераторы это одна из самых неочевидных вещей в языке. Большую часть конструкций можно описать используя другие конструкции. Получится многословно, но довольно очевидно. А для того, чтобы разобрать в более простые конструкции генератор, придётся писать огромный и нечитаемый switch-case и сопровождающий его код.

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