Categories
Modern JavaScript

Best of Modern JavaScript — Iterators and Iterables

Spread the love

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at JavaScript iterable objects.

Iterators that are Iterable

We can move the next function into its own object method if we return this in the Symbol.iterator method.

For instance, we can write:

function iterate(...args) {
  let index = 0;
  const iterable = {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      if (index < args.length) {
        return {
          value: args[index++]
        };
      } else {
        return {
          done: true
        };
      }
    },
  };
  return iterable;
}

We have the next within the iterable object we return.

The args have the items that we want to iterate over.

We return the object with the value while the index is less than args.length .

And we return an object with done set to true if there’s nothing left to iterate.

We return this inside the Symbo.iterator method so that the iteration can be done.

for-of loops work only with iterables and not the iterators directly.

Iterables always have the Symbol.iterator method, so we got to put our iterator over our iterable object to make it iterable.

return() and throw()

There’re 2 iterator methods that are optional.

return gives us an iterator the opportunity to clean if iterator ends early.

throw lets us forward a method call to a generator that’s iterated via yield* .

Closing Iterators

We can use return to close an iterator.

For instance, if we have the iterate function that we have before.

Then we can call break to end the for-of loop cleanly:

for (const x of iterate('foo', 'bar', 'baz')) {
  console.log(x);
  break;
}

return must return an object.

This is because of how generators handle the return statements.

Some constructors close iterators that aren’t completely clean up.

They include:

  • for-of
  • yield*
  • destructuring
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()
  • Promise.all(), Promise.race()

Combinators

Combinators are functions that combine existing iterables to create new ones.

For example, we can create one by writing:

function combinator(n, iterable) {
  const iter = iterable[Symbol.iterator]();
  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      if (0 < n) {
        n--;
        return iter.next();
      } else {
        return {
          done: true
        };
      }
    }
  };
}

We create a function to return the first n items from the iterable object.

Then we can use it by writing:

const arr = ['foo', 'bar', 'baz', 'qux'];
for (const x of combinator(3, arr)) {
  console.log(x);
}

This will log the first 3 items of the arr array.

Infinite Iterables

Iterable can return an infinite amount of values.

For instance, we can write:

function evenNums() {
  let n = 0;
  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      return {
        value: (++n) * 2
      };
    }
  }
}

to create an iterable object that returns even numbers.

Then we can create it by writing:

const nums = evenNums()
console.log(nums.next());
console.log(nums.next());
console.log(nums.next());

We call the evenNums function to create iterator.

Then we call next on each iterator to generate the numbers.

Conclusion

We can create iterable objects that return a finite and infinite number of values.

Leave a Reply

Your email address will not be published.

If you like the content of this blog, subscribe to my email list to get exclusive articles not available to anyone else.