Custom Object Iterators in JavaScript

Imagine that you have an object in JavaScript that maintains a list of items, and you want the object itself to be iterable, but the object also has other important properties - such as metadata about the list of items.

Here's a fun and completely original example.

const se7enDwarves = {
    employment: "Miners",
    output: "Diamonds",
    members: new Map()
}
se7enDwarves.members.set(1, "Sleepy")
se7enDwarves.members.set(2, "Grumpy")
se7enDwarves.members.set(3, "Happy")
se7enDwarves.members.set(4, "Bashful")
se7enDwarves.members.set(5, "Sneezy")
se7enDwarves.members.set(6, "Doc")
se7enDwarves.members.set(7, "Dopey")

I now have an object that represents the seven dwarves. It holds their names in an iterable collection, but also other relevent data about them. I would love to be able to iterate over the object itself, instead of having to type in that horrible, troublesome property namespace of members.

I want to see the names of all the dwarves, so I use the spread syntax to iterate the object and place the items in an array.

[...se7enDwarves]

VM2417:1 Uncaught TypeError: se7enDwarves is not iterable
    at <anonymous>:1:5

JavaScript, in all kinds of holy righteousness, tells me that I can't iterate an object like that.

Hmph.

Luckily, JavaScript allows developers to perform what is called metaprogramming. It's a feature of the language that allows us to modify some of the default behaviors of the underlying data structures available to us. I can make an object iterable.

se7enDwarves[Symbol.iterator] = function* () {
  const states = se7enDwarves.members.values()
  let state = null
  do {
    state = states.next().value
    if (typeof state !== "undefined") yield state
  } while (typeof state !== "undefined")
}

High level overview... I made the object iterable. Thought I'd point that out in case it wasn't obvious from that "self-documenting code".

[...se7enDwarves]

(7) ["Sleepy", "Grumpy", "Happy", "Bashful", "Sneezy", "Doc", "Dopey"]

Try it Out on jsfiddle


Update

Kushan Joshi provided an update to this code on dev.to that simply defers the generator to another generator. A nice solution.

se7enDwarves[Symbol.iterator] = function* () {
  yield * se7enDwarves.members.values();
}

[...se7enDwarves]

(7) ["Sleepy", "Grumpy", "Happy", "Bashful", "Sneezy", "Doc", "Dopey"]