Iterables

Iterators

 

 

 

Samuel Silva

What are going to talk about:

- What's an Iterable/Iterator.

- How to implement it.

- Why is it important.

 

Iterable/Iterators

Introduction

const myFavouriteAuthors = [
  'Neal Stephenson',
  'Arthur Clarke',
  'Isaac Asimov', 
  'Robert Heinlein'
];

How to get back all elements individually?

for(let i = 0; i < myFavouriteAuthors.length; i++) {
    console.log(myFavouriteAuthors[i]);
}

let i = 0;
while (i < myFavouriteAuthors.length) {
    console.log(myFavouriteAuthors[i]);
    i++;
}

for(const value of myFavouriteAuthors) {
    console.log(value);
}

Now, imagine that instead of the previous array, you had a custom data structure to hold all your authors. Like this —

const myFavouriteAuthors = {
    allAuthors: {
        fiction: [
            'Agatha Christie',
            'J. K. Rowling',
            'Dr. Seus'
        ],
        scienceFiction: [
            'Neal Stepenson',
            'Arthur Clark',
            'Isaac Asimov',
            'Samuel Castro'
        ],
        fantasy: [
            'J. R. R. Tolkien',
            'J. K. Rowling',
            'Terry Pratcheet'
        ]
    }
}

for (let author of myFavouriteAuthors) { 
  console.log(author)
}

// TypeError: {} is not iterable

We can just create a custom method that iterates over our array:

const myFavouriteAuthors = {
    allAuthors: {...},
    
    getAllAuthors() {
        const authors = [];

        for(const author of this.allAuthors.fiction) {
            authors.push(author);
        }
        
        ...

        return authors
    }
}

​- It won't work natively with for/of (unless we call getAllAuthors)

- The name getAllAuthors is very specific, I want something generic, that JavaScript would understand, and anyone could use without read the object, a pattern.
 

- getAllAuthors returns an array of strings of all the authors. What if another developer returns an array of objects in this format.

 

- An iterable is a data structure that wants to make its elements accessible to the public. It does so by implementing a method whose key is Symbol.iterator. That method is a factory for iterators. That is, it will create iterators.​

 

- An iterator is a pointer for traversing the elements of a data structure.

Making objects iterable

const iterable = {

    [Symbol.iterator]() { // An iterable should implement the Symbol.iterator function
        let step = 0;
		
        const iterator = {
            next() {
                step++;
		if (step === 1)
	            return {value: 'This', done: false};
		else if (step === 2)
	            return {value: 'is', done: false};
		else if (step === 3)
	            return {value: 'iterable', done: false};

	      return {value: undefined, done: true};
	    }
        }
		
        return iterator;
    }
}

Invoking Iterables

const iterator = iterable[Symbol.iterator]();

console.log(iterator.next()); // {value: 'This', done: false}
console.log(iterator.next()); // {value: 'is', done: false}
console.log(iterator.next()); // {value: 'iterable', done: false}
console.log(iterator.next()); // {value: undefined, done: true}

Calling iterator directly

for/of

// Using for/of
for(const value of iterable) {
    console.log('for/of', value)
}
// Using spread operator
console.log('spread operator', ...iterable); // This is iterable

Spread Operator

Destructuring Assignment

// Using Destructuring assignment

const [a, b, c] = iterable

console.log('Destructuring assignment', a, b, c) // This is iterable

Iterables in JavaScript

A lot of things are iterables in JavaScript. It may not be visible immediately, but if you examine closely, iterables will start to show.

 

These are all iterables —

- Arrays and TypedArrays

- Strings — iterate over each character or Unicode code-points.

- Maps — iterates over its key-value pairs

- Sets — iterates over their elements

- arguments — An array-like special variable in functions

- DOM elements (Work in Progress)

const arr = [1, 2, 3]
const iteratorArray = arr[Symbol.iterator]();

console.log(iteratorArray.next()) // value: 1, done: false
console.log(iteratorArray.next()) // value: 2, done: false
console.log(iteratorArray.next()) // value: 3, done: false
console.log(iteratorArray.next()) // value: undefined, done true

How to directly invoke native iterables

Array

Map

const map = new Map([[1, 'one'], [2, 'two']]);
const mapIterator = map[Symbol.iterator]();

console.log('Map.get', map.get(1)) // one
console.log('next()', mapIterator.next()) // {value: Array(2), done: false}
console.log('next()', mapIterator.next()) // {value: Array(2), done: false}
console.log('next()', mapIterator.next()) // {value: undefined, done: true}
const myFavouriteAuthors = {
  allAuthors: {
    fiction: [
      'Agatha Christie', 
      'J. K. Rowling',
      'Dr. Seuss'
    ],
    scienceFiction: [
      'Neal Stephenson',
      'Arthur Clarke',
      'Isaac Asimov', 
      'Robert Heinlein'
    ],
    fantasy: [
      'J. R. R. Tolkien',
      'J. K. Rowling',
      'Terry Pratchett'
    ],
  },
  [Symbol.iterator]() {
    // Get all the authors in an array
    const genres = Object.values(this.allAuthors); // [fiction, scienceFiction, fantasy]
    
    // Store the current genre and author index
    let currentAuthorIndex = 0;
    let currentGenreIndex = 0;
    
    return {
      // Implementation of next()
      next() {
        // authors according to current genre index
        const authors = genres[currentGenreIndex];
        
        // doNotHaveMoreAuthors is true when the authors array is exhausted.
        // That is, all items are consumed.
        const doNothaveMoreAuthors = currentAuthorIndex >= authors.length;
				
        if (doNothaveMoreAuthors) {
          // When that happens, we move the genre index to the next genre
          currentGenreIndex++;
          // and reset the author index to 0 again to get new set of authors
          currentAuthorIndex = 0;
        }
        
        // if all genres are over, then we need tell the iterator that we 
        // can not give more values.
        const doNotHaveMoreGenres = currentGenreIndex >= genres.length;
				
        if (doNotHaveMoreGenres) {
          // Hence, we return done as true.
          return {
            value: undefined,
            done: true
          };
        }
        
        // if everything is correct, return the author from the 
        // current genre and incerement the currentAuthorindex
        // so next time, the next author can be returned.
        return {
          value: genres[currentGenreIndex][currentAuthorIndex++],
          done: false
        }
      }
    };
  }
};

for (const author of myFavouriteAuthors) {
  console.log(author);
}

console.log(...myFavouriteAuthors)

const [a, b] = myFavouriteAuthors

console.log(a, b);

Making myFavouriteAuthors iterable

Why Iterables/Iterators are important?

- Iterators are a pattern or a contract that JavaScript will use to understand how to iterate on many different data structure without know or understand how it's implemented. (Map, Arrays, Set, String, Custom Objects)

 

- Iterators will help us understand Generators.

 

- And we will use Generators a lot when we talk about Redux-Sagas (An alternative side effect model for Redux apps)

Takeaway

  • Iterator is a design pattern or a specification that describe how data structures should be iterated.
  • An Iterable is a data structure that implements Symbol.iterator function.
  • Symbol.iterator is a unique symbol used to maintain a consistent name for iterators.
  • An iterator is a pointer for traversing the elements of a data structure.
  • We can create our own custom iterables objects.
  • Native iterables data structures are: Array, String, Map, Set, arguments

 

Upcoming

Generators

function* idMaker() {
  const index = 0;

  while(true)
    yield index++;
}

var gen = idMaker();

console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2

Questions?

Iterables, Iterators

By Samuel Silva

Iterables, Iterators

  • 127