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
- Iterators (data producers)
- Observers (data consumers)
- 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)