DEV Community

Cover image for Advanced js: iterable objects (@@iterator)
Manuel Artero Anguita 🟨
Manuel Artero Anguita 🟨

Posted on

Advanced js: iterable objects (@@iterator)

Iterate over data structures is like Programming 101.

This just works:

for (const item of [1,2,3]) { 
  console.log(item)
}
// 1
// 2
// 3
Enter fullscreen mode Exit fullscreen mode

or using a Map

const map = new Map()
m.set('a', 'first value')
m.set('b', 'second value')

for (const [key, value] of m) {
  console.log(key + " » " + value)
} 
// a » first value
// b » second value
Enter fullscreen mode Exit fullscreen mode

But, not saying anything new, Objects are not iterable

const obj = { a: 'item a', b: 'item b' } 

for (const [key, value] of obj) {
  console.log(key + " » " + value)
}
// ⚠️ Uncaught TypeError: obj is not iterable
Enter fullscreen mode Exit fullscreen mode

why? 🤔

Array, Map, Set... built-in objects implement the iterable protocol while Object doesn't.

Summarizing official docs, any object (or one of the objects up its prototype chain) which defines the @@iterator method is iterable.

@@iterator method is just notation. A convention for naming "a method that is accessible through Symbol.iterator symbol that [[fulfills the Iteration protocol]]"

... wait what? 🙃

This notation: Foo[@@someKey] is naming the property of Foo accessible through Symbol.someKey; easier to understand with code:

{
  ...
  [Symbol.someKey]: /* we're naming this property */
}
Enter fullscreen mode Exit fullscreen mode

Really I haven't found any official mention in the docs explaining this notation but inferred from usage across the docs.

When the docs refers to @@iterator:

{
  ...
  [Symbol.iterator]: () => {
    /* we need to implement this method */
  } 
}
Enter fullscreen mode Exit fullscreen mode

Now, js needs this method to... return an object with a method named next() that returns bla bla bla; something like this:

{
  ...
  [Symbol.iterator]: () => {
    return {
      next() {
        ...
        return { done: true, value: i };
      },
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

OR (and this is really cool) this function can be a Generator function

from the docs:

The Generator object is returned by a generator function and it conforms to both the iterable protocol and the iterator protocol.

{
  ...
  *[Symbol.iterator]() {
    /* a generator function here does the trick */
    yield i
  }
}
Enter fullscreen mode Exit fullscreen mode

To Be Honest: I have never found the chance to use generator functions in real life... they are just one more tool in my toolbox 🧰

Assemble! 🦸🏾‍♂️

An object that defines (in @@iterator) a function that yields each [key, value], is iterable:

const obj = {
  a: 'first value',
  b: 'second value',

  *[Symbol.iterator] () {
    for (const k in this) {
      yield [k, this[k]]
    }
  },
}

for (const [key, value] of obj) {
  console.log(key + " : " + value)
}
// a : first value
// b : second value
Enter fullscreen mode Exit fullscreen mode

Since it's iterable, it may also use destructuring:

const obj = {
  a: 3,
  b: 100,

  *[Symbol.iterator] () {
    for (const k in this) {
      yield [k, this[k]]
    }
  },
}

[...obj].find(([_, value]) => value > 99 )
// Array [ "b", 100 ]
Enter fullscreen mode Exit fullscreen mode

I've uploaded to npm a tiny utility package that does this for you in case you are curious:

Image description

import { iterable } from "@raw-js/iterable";

const obj = { ... }

/* for..of */
for (const [key, value] of iterable(obj)) {
  console.log(key + ": " + value);
}

/* destructuring */
[...iterable(obj)]
Enter fullscreen mode Exit fullscreen mode

Final thoughts 🧳

Do I include this method on the prototype chain of my objects, in order to make them iterable?

NO.

I find this an interesting exercise for deep-understanding js. But that's it. This is a "how does this work" exercise.

In real life, js does implement this idea thanks to Object.entries()

for (const [key, value] of iterable(obj)) {}

===>

for (const [key, value] of Object.entries(obj)) {}
Enter fullscreen mode Exit fullscreen mode

Implementing @raw-js/iterable helped me understanding more about Symbols, Iterators, Generators, etc. But in my day to day work I'd prefer Object.entries()


Banner image from Storyset

Thanks for reading 💚.

Top comments (0)