Back to All Posts

More Elegant Destructuring with JavaScript Generators

Exploring one of the few practical-ish use cases for iterables and generators I’ve come across so far — destructuring an arbitrary number of entities on demand.

JavaScript has a standard for defining an object’s iteration behavior. In fact, it’s at work whenever you destructure an array or use it in a for..of loop.

const arr = ['first', 'second', 'third'];

// Using destructuring: 
const [first, second, third] = arr;

// Using a `for..of` loop: 
for(const item of arr) {
	doStuff(item);
}

But it’s not just for built-in objects. You can make any object iterable by giving it a Symbol.iterator function property that returns an object adhering to the iterator protocol. In its most basic form, that returned object has next() method returning another object with value and done properties.

For example, here’s an iterable that provides a range of numbers (1 through 3):

let count = 1;

const iterableObj = {
  [Symbol.iterator]() {
    return {
      next() {
        return {
          done: count === 4,
          value: count++,
        };
      },
    };
  },
};

for (const item of iterableObj) {
  console.log(item);
}

// 1
// 2
// 3

Correctly setting that done property is critical, because that’s how the for loop will know when to discontinue execution, or, if you’re destructuring, when you’ll start getting undefined values:

const [a, b, c, d] = iterableObj;

// 1, 2, 3, undefined

Interesting, but… practical?

While all that’s cool, there’s a notorious lack of “real” use cases for custom iterables in the wild. At least, that’s the story as I’ve experienced it.

And then I saw this tweet from Alex Reardon. In his example, an iterator and destructuring are used to generate an arbitrary number of objects on the fly (DOM elements, in his case). Here’s that implementation (a smidge modified, but the same spirit):

function getElements(tagName = 'div') {
  return {
    [Symbol.iterator]() {
      return {
        next() {
          return {
            done: false,
            value: document.createElement(tagName),
          };
        },
      };
    },
  };
}

const [el1, el2, el3] = getElements('div');

console.log(el1, el2, el3); 
// HTMLDivElement, HTMLDivElement, HTMLDivElement

At first, it looked unnecessarily complicated, especially when considering what I’d personally otherwise do with an array:

function getElements(tagName = 'div', number) {
  return new Array(number).fill(null).map(i => document.createElement(tagName));
}

const [el1, el2, el3] = getElements('div', 3);

console.log(el1, el2, el3);
// HTMLDivElement, HTMLDivElement, HTMLDivElement

Despite needing to specify how many items you’d like to generate up front, this sort of approach had always satisfied me (despite .fill() always feeling a little weird). But then I remembered that there’s more than one way to make your own iterable in JavaScript.

Generators: Write Simpler Iterables

If using Symbol.iterator is too verbose, you can syntactically sweeten it with a generator, a special function returning a Generator object that functions the same way as a hand-written iterable. Here’s that same method written as a generator function:

function* getElements(tagName = 'div') {
  while (true) {
    yield document.createElement(tagName);
  }
}

const [el1, el2, el3] = getElements('div');

console.log(el1, el2, el3);

That infinite while loop may be startling, but it’s safe — the yield keyword won’t allow it to continue to run until the generator is invoked each time, whether it be via for..of (which you can even use asynchronously), destructuring, or something else.

Some Perks to Destructuring with a Generator

This change made it much more appealing to use iterables in destructuring items, for a few reasons:

  1. I don’t need to specify how many items I want to generate up front. As such, the function’s signature is a little simpler (the number parameter is no longer required).
  2. There’s a (small) performance advantage. Items are produced on demand, not up front. I’ll never accidentally generate five items, end up only needing three, and leaving two orphans.
  3. In my opinion, it’s more elegant than filling an array. You can even get the function body down to a single line:
function* getElements(tagName = 'div') {
  while (true) yield document.createElement(tagName);
}

And in addition to those benefits, an honorable mention: the increased social credibility that comes with telling people you’ve had a reason to use a generator.

Time Will Yield More

When you’ve used a certain set of tools for countless jobs, it’s not easy to be given a new tool and expect to immediately know how it’s uniquely suited to solve certain problems. So, despite this being one of the first “practical” use cases I’ve for a generator, I don’t expect it to be the last. After all, it’s still a new tool for me.

That said, I’m eager to hear about other reasons iterators and generators are useful for a job, no matter how insignificant. If you have ‘em, yield ’em!


Alex MacArthur is a software engineer working for Dave Ramsey in Nashville-ish, TN.
Soli Deo gloria.

Get irregular emails about new posts or projects.

No spam. Unsubscribe whenever.
Leave a Free Comment

5 comments
  • Luciano

    I didnt know that the array deconstruction could be invoked on generators functions. That is a nice one to have knowledge of. I find it really rare to use generator functions, but they're pretty useful on some cases, so thanks for bringing it up. :D


  • bielsama

    Just for knowledge, a more suitable method would be:

    function getElements(tagName, length) {
      return Array.from({ length }, () => document.createElement(tagName));
     }

  • Alberto melchor herrera

    Interesting. I know about generators and I am interested on them. I used to proceed with Array.from to convert an iterator to an array. I may try destructuring instead.
    I have personally made an iterator for walk throug a node tree. I can use it to search for a node, make a node tree based in files folder structure, find out a node level... Well is very usefull for me, if somebody is interested can find me in GitHub : petazeta


  • Hop

    Good article. If you want to see a very wide use case where generator functions and custom iterables shine, have a look at a the "js-coroutines" module on NPM (note: I am not the author, I just use generators a lot and I'm impressed by it). It's a tool for co-operative scheduling and it offers methods for writing generator-based high-priority sequential animations, as well as methods for writing low-priority incremental computations (as well as primitives for sorting data, LZ compressing JSON etc). You could use it to keep animations snappy at 60FPS in a scenario where a lot of number crunching needs to be done (perfect example being something like a spreadsheet widget with localstorage persistance or a similarly complex UI element).


  • Ben

    fwiw, Array(num).fill(elem) would create an array with the exact same dom element in each slot. In that example, you'd actually want to do


    Array(num).fill(anything).map(() => document.createElement('div'))

    Love generators! I tried to use those for all of the advent of code projects this past December.


    1 reply
    • Alex

      Whoa, great catch! I assumed each node would be unique, but totally makes sense that it doesn't. Gonna update this.