Skip to content

Latest commit

 

History

History
189 lines (139 loc) · 7.61 KB

README.md

File metadata and controls

189 lines (139 loc) · 7.61 KB

Basics

Iterators

An iterator is a function that an object provides to loop/run over it or something in it.

For example, if we want to loop over individual letters that are in an array, the first idea that comes to mind is to use the for-of keyword, like this:

const arrayOfLetters = ['A', 'B', 'C']

for (const letter of arrayOfLetters) {
  console.log(letter) // A, B, C
}

It seems that the "magic" 💫, the ability to iterate over the array, exists within the for-of keyword. But this is not the case; it is the array itself that provides this ability.

In other words, any object that wants to make it possible to iterate over it has to provide this functionality itself. What it provides, we call an Iterator.

It's worth noting that in ES6, most built-in data structures including Arrays, Maps, and Sets, already incorporate the "Iterator Protocol", which means you can iterate over them using the for-of loop right out of the box.

This is a handy feature, allowing developers to directly loop through collections without the need to implement an Iterator manually.

This highlights the power and flexibility of Iterators in modern JavaScript programming.

Iterator Protocol

To provide an iterator, an object must return a function at the object-property/key Symbol.iterator. This is a well-known Symbol, in this context a static property and part of the "Iterator Protocol".

An implementation of that looks like this:

const ownObject = {
  [Symbol.iterator]: () => {
    // The Implementation
  }
}

The user of the iterator, or the "caller", does so like this:

const iterator = arrayOfLetters[Symbol.iterator]()

Be cautious. This line of code calls the function associated with the object property/key Symbol.iterator. However, it does not automatically start the iteration process. The function at object-property/key Symbol.iterator returns an object, referred to as an iterator. Among other properties, this iterator provides a next function. This next function must be called subsequently to retrieve the results of each iteration.

Iterator Functionality

To run/start an iteration, we need a bit more. In this case, another function that does the iteration or provides the functionality to iterate. An essential part of the "Iterator Protocol" is the next function.

This next function does whatever needs to be done to iterate and returns this kind of object each time we call it:

// Definition
type ResultOfNext = {
  value: any | undefined
  done: boolean
}

And yes, I am aware that any includes undefined 🤷‍. But with this explicit definition, I want to emphasize that the next function must return { value: undefined, done: true } if there's nothing more to iterate, or there are other cases why no more value should be provided.

Given the complexity of this concept, let's preview an example of an object ownObject with a Symbol.iterator function. In the iterator function, a next function is returned. This next function, when invoked, returns a letter from the foo property of ownObject for each call until it has returned all the letters. Once all letters have been returned, it indicates completion by returning { value: undefined, done: true }.

const ownObject = {
  foo: 'bar',
  [Symbol.iterator]: function () {
    let counter = 0;
    
    return {
      next: () => {
        if (counter === this.foo.length) {
          return { value: undefined, done: true };
        }
        return { value: this.foo[counter++], done: false };
      }
    }
  }
}

And there has to be another side to this: the one calling each iteration (the "caller") of the next function, like this:

iterator.next() // { value: 'A', done: false }

To comply with the "Iterator Protocol", every call of the next function returns an object that corresponds to the definition described (as ResultOfNext above).

As you can imagine, to get all the letters from the array we started this description with, you have to call the next function multiple times – like this:

iterator.next() // { value: 'A', done: false }
iterator.next() // { value: 'B', done: false }
iterator.next() // { value: 'C', done: false }
iterator.next() // { value: undefined, done: true }

Conclusion

  • Any object that wants to make it possible to iterate over it has to provide this functionality itself.
  • To have a standard way of behaving, you have to follow the Iterator Protocol.
  • To follow the Iterator Protocol you have to provide:
    • a function at the object-property/key Symbol.iterator
    • This function returns, among other things, a next function
    • This next function returns the object definition { value: any | undefined, done: boolean } every time it is called
    • The value can be anything but must be undefined when the iterations end
    • The value of done must be false if the iterator has more iterations to provide, otherwise true.
    • The JavaScript types Array and String have already built in an iterator-function that can be used.
    • If you try to iterator over an object without a implementation for an iterator, you get this error:
      Type WHATEVER must have a [Symbol.iterator]() method that returns an iterator.

Using an Iterator with the for-of loop / The "magic" 💫

Think back to our code, where this description started:

const arrayOfLetters = ['A', 'B', 'C']

for (const letter of arrayOfLetters) {
  console.log(letter) // A, B, C
}

With your knowledge about the Iterator Functionality and the Iterator Protocol, you can perceive that the for-of keyword, under the hood:

  • Gets an iterator (like arrayOfLetters[Symbol.iterator]()).
  • Calls the next function until the end.
  • Saves the value in the variable provided (in our case letter).
  • Identifies the end of the next function based on the returned object { value: undefined, done: true }.

Generators

Please look at the code of the test suite.

👀 What to find

Iterators

  • A test suit that shows…
    • how an iterator is used,
    • what happens if there's no iterator and
    • how to implement your own iterator.

Generators

  • A test suit that shows…
    • the syntax of a generator (function*)
    • the implementation of a generator
    • that a generator generates or provides an iterator

📚 Further readings / watching

❤️ Thanks & Love to…