Creating An Iterator Function By Scratch in JavaScript

Picture shows Jason A. Martin - Software Engineer, Indie Game Developer, Tech Evangelist, Entrepreneur.

In this JavaScript code snippet, I show how to create a custom iterator function. Generally, you're probably going to use a generator function when you want to step through a data stream, but today I'm going to jot down a little code note about creating a custom iterator in JavaScript easily, thanks to closure.

There are many reasons to use a generator, but my specific case is thinking about iterating through a collection of article files so that I can pull them out one at a time as needed in more readable fashion.

NORMAL GENERATOR

Let's first look at a simple generator function (this is an ES6 feature).

function* coolGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

coolGenerator(); // output: { value: 1, finished: false }
coolGenerator(); // output: { value: 2, finished: false }
coolGenerator(); // output: { value: 3, finished: false }
coolGenerator(); // output: { value: undefined, finished: true }

Each time you call the generator function, it resumes where it left off and executes until the next yield. After the final yield, it just returns undefined with a finished state of true.

OUR ITERATOR

We're going to create a simple iterator by passing in an array, returning out a function with a next() property and using closure to keep track of state. Additionally, we'll modify it to pass the finished object value as a generator does (except ours will pass out finished: true on the last call in the array).

const articles = ['readme.md', 'test.md', 'coolOne.md', 'fun.md'];

function getData(array){
  let i =0;
  const nextYield = {
    next: function() {
            const element = array[i];
            i++;
            return { value: element, finished: (i >= array.length) };
          }
  }
  return nextYield;
}

Now that we have it setup, we're ready to call it. I also want to have two streams going (for whatever reason).

const allArticles = getData(articles);
const processArticlesAgain = getData(articles);

allArticles.next(); // { value: 'readme.md', finished: false }
allArticles.next(); // { value: 'test.md', finished: false }
allArticles.next(); // { value: 'coolOne.md', finished: false }
allArticles.next(); // { value: 'fun.md', finished: true }

processArticlesAgain.next(); // { value: 'readme.md', finished: false }

As you can see, we did four calls to allArticles.next() and got our four articles along with a finished: true on the last call.

Additionally we called processArticlesAgain.next() and got another stream going.