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)