Generators 1


for ... of
перебор array, set, iterable

const a = [1, 2, 3]
for (let x of a) {
  console.log(x)
}
Для сущности, которую можно перебрать должен быть реализован [Symbol.iterator]() {}
const someWeirdObject = {
  position: 1,
  [Symbol.iterator]() {
    let position = 1
    const obj = {
      next() {
        return {
          value: position++,
          done: position > 10,
          next: obj.next
        }
      }
    }
    return obj
  }
}
function* demo() {
  console.log('start')
  yield 1 // останавливает выполнение до next и возвращает 1
  console.log('going 1')
  yield 2
  console.log('going 2')
  yield 3
  console.log('going 3')
  return 4 // игнорируется зачастую
}

const ier = demo()
iter.next()
[...demo()]
for (let x of demo()) {console.log(x)}

Use cases

  1. Iterators (data producers)
  2. Observers (data consumers)
  3. Coroutines (mixed producers/consumers)
function* objectEntries(obj) {
  const propKeys = Reflect.ownKeys(obj)

  for (let propKey of propKeys) {
    yield [propKey, obj[propKey]]
  }
}

const a = {a: 1, b: 2, c: 3}
for (let [key, value] of objectEntries(a)) {console.log(key, value)}

Делегирование генератора

function* gen1() {
  yield 1
  yield 2
  yield* gen2() // принимает на вход любую интерируемую сущность
  yield 3
}

function* gen2() {
  yield 4
  yield 5
  yield 6
}

Обход дерева - красивое применение генераторов

class Tree {
  constructor(value, left = null, right = null) {
    this.value = value
    this.left = left
    this.right = right
  }

  * [Symbol.iterator]() {
    if (this.left) yield* this.left
    yield this.value
    if (this.right) yield* this.right
  }
}

let a = new Tree(
  8,
  new Tree(
    5,
    new Tree(2),
    new Tree(6)
  ),
  new Tree(12)
)

yield можно делать только из генератора

function* test() {
  [1, 2].forEach(x => yield x) // не сработает
}

function* sum() {
  let sum = 0
  while (1) {
    let currentNumber = yield sum
    sum += currentNumber
  }
}

let staticSum = sum()
staticSum.next(343432) // ignore first
staticSum.next(5)

staticSum.return(4567) // если хотим завершить и передаем значение
// как бы потом не дергали next - не сработает

// упрямый генератор
function* stubbornSum() {
  let sum = 0
  try {
    while(1) {
      let currentNumber = yield sum
      sum += currentNumber
    }
  } catch(e) {
    console.log(e)
  } finally {
    while(1) {
      let counter = 1
      yield `I'll be back ${counter++}`
    }
  }
}

let sum = stubbornSum()
sum.next()
sum.next(3)
sum.next(6)
sum.return('Go away') // {value: 'Ill be back 1', done: false}
sum.next(3)
sum.next(6)
sum.return('Go') // {value: 'Go', done: true}

Корутины

программы, которые выполняются (грубо говоря) параллельно и позволяют нам писать асинхронный код в синхронном стиле

function loadData() {
  fetch(url)
    .then(r => r.json())
    .then(data => {
      const ul = document.createElement('ul')
      data.forEach(({id, name}) => {
        const item = document.createElement('li')
        item.textContent = `${id}. ${name}`
        ul.appendChild(item)
      })
      document.body.appendChild(ul)
    })
}

loadData()

Хотелось бы писать так:

function* loadData() {
  const timeoutPromise = new Promise((ok, fail) => {
    setTimeout(Math.random() < 0.5 ? ok : fail, 2000)
  })

  try {
    yield timeoutPromise    
  } catch(e) {
    document.body.textContent = 'Error!'
    return
  }

  const data = yield fetch(url)
  const ul = document.createElement('ul')
  data.forEach(({id, name}) => {
    const item = document.createElement('li')
    item.textContent = `${id}. ${name}`
    ul.appendChild(item)
  })
  document.body.appendChild(ul)
}

function co(generatorFn) {
  const iterator = generatorFn()

  function step(promiseResult = undefined) {
    const nextPromise = iterator.next(promiseResult)
    if (!nextPromise.done) {
      nextPromise.value
        .then(
          result => step(result),
          result => iterator.throw(result)
        )
    }
  }

  step()
}

co(loadData)

tj co

generators in depth