Tyler Roberts

Posted on

# JS Symbol Iterators & Generators - An Exercise

## Brief

When you iterate over lists it's probably intuitive now for most to use Array.map(). However, many of us also like to generate our lists based on some range, not by way of data but some application logic defined number. Usually, I'll import lodash.range or create a range helper. What about specific sequences like Fibonacci? Well here we can take the power of large, possibily infinite size lists. Normally large lists would hurt performance if it's particularly large even in O(n).

Essentially we're creating a lazy loaded sequence.

In a lot of libraries today we have lazy load as a common way of handling lists of data that may be large in length or in size per element; image galleries.

Without writing a lot of helpers, importing libraries, or getting caught in cumbersome type associations as boilerplate, we can look at built-in Generators.

Now when we define our applications sequences or even the json we may use, we can immediately "close the faucet" of the flow of that data. Only opening it when we need it, making it reusable, and allowing us to completely throw it out if we need to start over.

Given a list of data we can look at a list of lists to get started:

const familyTree = [
["Jane", "Peter", "Mary"],
["Mary", "Liam", "Olivia"],
["William", "Ava", "Lucas"]
]

Here we have a "sorted" list going from familyTree[0] being the earliest generation and last index being the oldest.

Let's assume that the first of each is the "Child" and the other two are biological "Parents".

# Iterator Logic

Let's start by creating our familyTree iterator logic.

function* genList(p1, p2) {
const genealogy = [...familyTree].reverse();
}

I choose to work backwards from generation, given our data, and spread operator to prevent mutation.

In this data our familyTree contains the newest generation at the head or first of the list. So we can reverse the list before we start.

### yield*

We could easily create a map of each element very quickly with yield* to simply "iterate" the given data, and give us each Array within familyTree, but where's the fun in that. Our generator should have some logic, and yield genealogy[i] conditionally!

To clarify what * does, we can look at yield*:

function* genList() {
yield* [...familyTree].reverse();
}

let i = genList();
console.log(i.next().value); //  [ 'William', 'Ava', 'Lucas' ]
console.log(i.next().value); //  [ 'Mary', 'Liam', 'Olivia' ]

Now let's search to find who we're actually looking for with p2 or person2

• Let's imagine it's "Olivia"
• ["William", "Ava", "Lucas"] is first since we reversed, so we can skip over it

### Reverse the data

function* genList(p1, p2) {
const genealogy = [...familyTree].reverse();
let start = 0, end = genealogy.length - 1;
for (const i in genealogy) {
if (genealogy[i].includes(p2)) {
start = +i // coerce typeof i to number from string
}
if (genealogy[i].includes(p1)) {
// Exercise: what would go here, and why?
}
}

Now that we can rule out names that aren't even in here.

Let's loop through our reduced list by finding links in each family array, for preceding arrays;

• or "blood" relation

### Did the child become a parent?

main.js

function* genList(p1, p2) {
[...]

// Iterator Logic
for (let i = start; i <= end; i++) {
// yield will send over the first family
// with .next(Child) we can pass over a name
// from the above: yield genealogy[i]
if (link && (i + 1) <= end) {
let [_, ...parents] = genealogy[i + 1]
// Did that child became a parent?
// Let's see if parents include the link
yield genealogy[i]
}
} else {
// but if there's no subsequent links...
break;
}
}
}

## Let's Apply it and Test

main.js

/**
*
* @param p1 Child
* @param p2 Relative
*/
const isGenerational = (p1, p2) => {

// generate genealogy with lower and upper bounds
const ancestry = genList(p1, p2)
// get Child from each family and yield links
for (const [ancestor] of ancestry) {
(ancestor === p1)
// if p1 is found, we can throw the list away

}
}

(async () => {
console.log(
(isGenerational("Jane", "Liam") === true),
(isGenerational("Mary", "Ava") === false),
)
})();

# Recap

### Destructuring parameters from next via yield

Let's look at this very peculiar statement.

It's the initialization that makes it useful.

We can send data over initially, and wait for any data that may be contextual.

// perform check, updates, hydrate, whatever
yield context["imaginary"]
}

Essentially when our function obtains something we can have our iterator pass it up to the generator and allocate a new yielded value.

const iterator = contextSequence(); // generates sequence of "context"
let water = iterator.next("hydrate"); // <- gets passed to `whatAboutThis`
water.value // -> value stored of context["imaginary"]

I can imagine reactive state handlers here. Imagine a federal reserve of some data that is only accessed when stores are low; lazy loaded galleries.

Great way to handle large queues that run async but not AOT.

I'm thinking debounce function for subscribed events that aren't time critical. Although I'll need to play around with it a bit. Almost every example showcases take on infinite lists, so it's very functional.