// Generators can return ("yield") multiple values, one after another, on-demand. They work great with iterables, allowing to create data streams with ease
function* generateSequence() {
yield 1
yield 2
return 3
}
// "generator function" creates "generator object"
let generator = generateSequence()
alert(generator) // [object Generator]
// generator execution starts on first next() and then pauses instantly, then on next next() call, it resumes execution of generator till the next "yield"
let one = generator.next()
alert(JSON.stringify(one)) // {value: 1, done: false}
let two = generator.next()
alert(JSON.stringify(two)) // {value: 2, done: false}
let three = generator.next()
alert(JSON.stringify(three)) // {value: 3, done: true}
// generator object is iterable, but it skips "return" and only considers "yield"
for(let value of generator) {
alert(value) // 1, then 2
}
// we can also use generators to create iterable objects
let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // a shorthand for [Symbol.iterator]: function*()
for(let value = this.from; value <= this.to; value++) {
yield value
}
}
}
alert( [...range] ) // 1, 2, 3, 4, 5
// to call a generator inside a generator use "yield*" and it will act as if we inlined the code from another generator (embed)
yield* anotherGenerator()
// "yield" is a two-way street; we can also use it to set yielded value from caller to generator using "generator.next(arg)"
function* gen() {
// pass a question to the outer code and wait for an answer
let result = yield "2 + 2 = ?"
alert(result)
}
let generator = gen()
let question = generator.next().value // line 1 - yield returns the value
generator.next(4) // line 2 - pass the result into the generator
/*
line 1 - gen() execution pauses after "yield" and stores string in "question" variable
line 2 - on generator.next(4), it resumes and sets "result" value to 4 because of double-sided "yield", then it alerts 4
*/
// use this to explicitly throw exception at a point in the paused generator
generator.throw(new Error("Oops! Error occured."))
// use this to instantly return from a generator
function* gen() {
yield 1
yield 2
yield 3
}
const generator = gen()
generator.next() // { value: 1, done: false }
generator.return('foo') // { value: "foo", done: true }
generator.next() // { value: undefined, done: true }
// Asynchronous iteration allow us to iterate over data that comes asynchronously, on-demand. Ex - chunk-by-chuk data of a big file, etc.
/*
To make an object iterable asynchronously:
Use Symbol.asyncIterator instead of Symbol.iterator
The next() method should return a promise (to be fulfilled with the next value) - async next()
To iterate over such an object, we should use a for await (let item of iterable) loop
*/
// Features that require sync iterators won't work with async iterators. So spread syntax and for...of loop won't work with async iterators
// Async Generators - prepend function* with asyn and then use for await (...) to iterate over it
// async generators can also replace async iterators just like their sync counterparts