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;
IteratorResultgives you the data and lets you know if the iterable is finished. It hasdoneandvalueproperties.IteratorThis object has anext()function that returns anIteratorResultobject. This is the part that does all the work.IterableThis object is required to have a function at the symbolSymbol.iteratorthat 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 newIteratorfor an array will always start from the first element and end with the last.
This last part confused the heck out of me;
IterableIteratorSome objects give values but aren't necessarily a collection. It doesn't make sense to be able to get a freshIteratorthat starts from the beginning.GeneratorFunctionfor example starts executing when called, and can't be restarted. It returns anIteratorobject 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;
IteratorResultan object with thedoneandvalueproperties.AsyncIteratorThis object has anext()function that returns a Promise for anIteratorResultobject.AsyncIterableThis object is requited to have a function at the symbolSymbol.asyncIteratorthat returns anAsyncIterator.AsyncIterableIteratoris anAsyncIteratorthat returns itself when it'sSymbol.asyncIteratorfunction 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 🤖.