clubmate.fi

A good[ish] website

Web development blog, loads of UI and JavaScript topics

JavaScript iterators tutorial with examples | introduction to the iterable object

Filed under: JavaScript— Tagged with: iteration

What’s an iterable object anyway? What does it meant to "implement the iterator protocol", and how to build a custom iterator.

Iterators in JavaScript are a relatively new new thing, they’ve been around for few years. Let’s just jump into it with examples.

What is an iterator?

Iterator is not an array, but it can behave a bit like an array, in the sense that you can iterate it over with a loop construct.

MDN article defines an iterator as such:

Specifically, an iterator is any object which implements the Iterator protocol by having a next() method that returns an object with two properties:

value
The next value in the iteration sequence.
done
This is true if the last value in the sequence has already been consumed. If value is present alongside done, it is the iterator’s return value.

So, what exactly is the iterator protocol then? That’s basically what this post tries to explore.

Dissecting the iterator

The for...of loop construct can work with iterables. If for...of would be a person, and you’d ask it, "What is an array?" it would be just like, "I don’t know 🤷‍♀️". See,for...of doesn’t exactly work in the array department of the JavaScript corporation. But, if you try to loop an array with it, it behaves like you’d assume it would:

const things = ['egg', 'thermometer', 'matryoshka', 'banana']

for (const thing of things) {
  console.log(thing)
  // egg thermometer matryoshka banana
}

So what’s up? It’s the iterator working behind the scenes. If you dir the things array console.dir(things), and look into its __proto__ property:

Array method’s prototype

See that Symbol.iterator there? It can be accessed like so:

const iterator = things[Symbol.iterator]()
console.log(iterator)

That above log tells us that the iterator has a method called next(), which is the heart of this whole thing. The next() method can be called manually and it’ll get the next item in the iterable (which in this case is the first item, because we’re calling it the first time):

console.log(iterator.next())
// Object
//   done: false
//   value: "egg"

It has those two properties mentioned earlier: done, and value.

If the next() is called more times than we have items in our test array, the done would eventually turn into true, signaling that we’re done here, nothing to iterate anymore:

console.dir(iterator.next().done) // false
console.dir(iterator.next().done) // false
console.dir(iterator.next().done) // false
console.dir(iterator.next().done) // false
console.dir(iterator.next().done) // true

So the for...of loop works by calling next(), then grabbing data from next().value, and checking if it should keep going from next().done. That is the iterator basically.

Make your own iterable object

In this example I’m not really doing anything special for the shape that I’m working with (bad imagination), but there’s a lot of changes to manipulate the data in many different ways. Constructing your own iterators give you a lot of freedom.

const things = {
  things: {
    egg: 'Chicken capsule',
    thermometer: 'Air spiciness gauge',
    matryoshka: 'Grandma multiplier',
    banana: 'Longular fruit'
  },
  [Symbol.iterator]: function () {
    let index = 0
    const keys = Object.keys(this.things)

    return {
      next: () => {
        const hasMore = index < keys.length

        if (hasMore) return { done: false, value: this.things[keys[index++]] }

        return { done: true }
      }
    }
  }
}

for (let thing of things) {
  console.log(thing)
}

See a simple demo in CodeSandbox:

Edit custom-iterator

Turn iterable object to an array

For example, a JavaScript Set is iterable, but it’s not an Array, it’s an Object:

const mySet = new Set(['foo', 'bar'])
console.log(typeof mySet) // object
// ❌ Won’t work obvs
const prefixedValues = mySet.map(x => '_' + x)

Maybe you want to use array methods on your iterable, like map or filter, you can very easily turn the iterable object into an Array by spreading it:

const prefixedValues = [...mySet].map(x => '_' + x) // ✅

Test if something is iterable

Here’s a simple helper function to test if something is iterable:

const isIterable = input => Boolean(input?.__proto__[Symbol.iterator])

Feed it data types:

console.log(
  isIterable({ foo: 'foo' }),
  isIterable(['foo', 'bar']),
  isIterable(new Map()),
  isIterable(new WeakMap())
)
Iterability of different JS data types
Data typeJS syntaxIs iterable
Booleanstrue|falseno
Undefined valuesundefinedno
Null valuesnullno
Objects{ foo: 'bar' }no
Strings'foo'yes
Empty strings''yes
Mapsnew Map()yes
Weak mapsnew WeakMap()no
Setsnew Set()yes
Weak Setsnew WeakSet()No
Generatorsfunction *foo () {}yes

Comments would go here, but the commenting system isn’t ready yet, sorry.

  • © 2022 Antti Hiljá
  • About
  • All rights reserved yadda yadda.
  • I can put just about anything here, no one reads the footer anyways.
  • I love u!