Async Generators
I started streaming-iterables
a few months ago to learn how to use async-generators
and it was hard. The concepts all sound very similar but there wasn't a great resource that spelled it all out, even MDN left me wanting more. In this post, I will layout the terminologies and show how they work, and then I'll follow up in another post with some examples using streaming-iterables
to taking advantage of how of it helps manage workflows.
Before I get too far, streaming-iterables
is the Swiss Army Knife I've always wanted for working with data input over time. With the release of v3, it's stable and faster than your streams. It has zero dependencies and is hardly any code. The magic is in the generator functions built into your runtime - and they're only getting faster.
If you want more details on sync Iterables
MDN has a pretty complete article. I'm going to assume some familiarity but lets start with naming the Iterable
building blocks;
IteratorResult
gives you the data and lets you know if the iterable is finished. It hasdone
andvalue
properties.Iterator
This object has anext()
function that returns anIteratorResult
object. This is the part that does all the work.Iterable
This object is required to have a function at the symbolSymbol.iterator
that returns anIterator
. It expects that each time you get a freshIterator
, it will start giving values from the start of the collection. For example, a newIterator
for an array will always start from the first element and end with the last.
This last part confused the heck out of me;
IterableIterator
Some objects give values but aren't necessarily a collection. It doesn't make sense to be able to get a freshIterator
that starts from the beginning.GeneratorFunction
for example starts executing when called, and can't be restarted. It returns anIterator
object to traverse its values. However it's much easier to work with anIterable
(I'll explain why) so it provides an iterator symbol that returns itself.
MDN describes for...of
as
"a loop iterating over iterable objects [...] It invokes a custom iteration hook with statements to be executed for the value of each distinct property of the object.
// an example for loop for an iterable array
const values = [1, 2, 3]
for (const value of values) {
console.log(value)
}
Under the hood the loop calls values[Symbol.iterator]()
to get an iterator
object and then calls next()
on that object to get values until the done
property is true. We can show how it works with a while
loop.
const values = [1, 2, 3]
// arrays being an Iterable always have an Symbol.iterator
const iterator = values[Symbol.iterator]()
while (true) {
const { value, done } = iterator.next()
if (done) {
break
}
console.log(value)
}
The for...of loop is a bit nicer.
We can do this same exercise with AsyncIterables
;
IteratorResult
an object with thedone
andvalue
properties.AsyncIterator
This object has anext()
function that returns a Promise for anIteratorResult
object.AsyncIterable
This object is requited to have a function at the symbolSymbol.asyncIterator
that returns anAsyncIterator
.AsyncIterableIterator
is anAsyncIterator
that returns itself when it'sSymbol.asyncIterator
function is called. For example the result of anAsyncGeneratorFunction
.
Using one in a for...await...of
loop.
// an example for loop for an async iterable
// getPokemon is an async generator that returns pokemon
// objects once per `setImmediate`
const { getPokemon } = require('iterable-pokedex')
const values = getPokemon()
for await (const pokemon of values) {
console.log(pokemon)
}
This loop looks about the same as our sync example. Unsurprisingly so will our while
loop.
const { getPokemon } = require('iterable-pokedex')
const values = getPokemon()
const iterator = values[Symbol.asyncIterator]()
while (true) {
const { value, done } = await iterator.next()
if (done) {
break
}
console.log(value)
}
This is straightforward and I'm happy to write it down all in once place.
Look for my upcoming post with some examples of using streaming-iterables
to make stream based workflows a little faster and easier to understand!
Powered by ⚡️ and 🤖.