More Elegant Destructuring with JavaScript Generators
Tags: javascript
Published: 2023-03-12 20:52:15

---
title: More Elegant Destructuring with JavaScript Generators
published: true 
date: 2023-02-25 05:00:00 UTC
tags: JavaScript
canonical_url: https://macarthur.me/posts/destructuring-with-generators
---

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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators#iterables). 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](https://twitter.com/alexandereardon/status/1534762394848636928). 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(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. 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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', 3);

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!