Skip to main content

Generators and Iterators in JavaScript

In JavaScript, iterators and generators are powerful tools that allow you to work with data in a more efficient and controlled way, especially when dealing with large datasets or complex iterations.

Generators

  • A JavaScript generator function is a special function that can pause and resume execution.
  • They are created using the function* syntax and can be paused and resumed at any point during their execution using the yield keyword.
  • When a generator function is called, it returns a generator object, an iterator.

Use cases:

  • Generators can handle large datasets without loading the entire set into memory.
  • They allow more complex iteration patterns than traditional loops or iterable constructs.
  • With the advent of async/await, built on generators, they can simplify asynchronous code by making it look more like synchronous code.

Basic Example

// Define a generator function
function* generatorExample() {
yield 1; // Pauses and returns 1
yield 2; // Pauses and returns 2
yield 3; // Pauses and returns 3
}

// Create a generator object
const gen = generatorExample();

console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
console.log(gen.next().value); // undefined

Infinite Generator Example

You can create a generator that never stops producing values:

function* infiniteGenerator() {
let index = 0;
while (true) {
yield index++;
}
}

const infGen = infiniteGenerator();

console.log(infGen.next().value); // 0
console.log(infGen.next().value); // 1
console.log(infGen.next().value); // 2
console.log(infGen.next().value); // 3

Generator with async/await

async function* asyncGenerator() {
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3'
];

for (const url of urls) {
const response = await fetch(url); // Await the async fetch call
const data = await response.json(); // Await the conversion to JSON
yield data; // Yield the data one at a time
}
}

(async () => {
const gen = asyncGenerator();
for await (const data of gen) {
console.log(data); // Logs each data object as it's received
}
})();

Iterators and Iterables

An iterator is an object that provides a way to access elements in a collection one at a time.

Think of it like a pointer that moves through the collection, giving you the current element each time you request it.

Built-in iterators exist for arrays, strings, maps, sets, and other iterable objects.

You can use the for...of loop to easily iterate over iterables.

Here is an analogy: Imagine iterating over a playlist. An iterator would be like a song player that goes to the next song each time you press a button.

Example

const numbers = [1, 2, 3, 4, 5];

const numberIterator = numbers[Symbol.iterator]();

console.log(numberIterator.next()); // { value: 1, done: false }
console.log(numberIterator.next()); // { value: 2, done: false }
console.log(numberIterator.next()); // { value: 3, done: false }

Create a Custom Iterator

  • To create a custom iterator, you need to define an object with a next() method that return an object object with two properties: value and done.
  • The value property holds the current item in the collection, and the done property is a boolean indicating whether the iteration has reached the end of the collection.
function createCustomIterator(array) {
let index = 0;

return {
next: function() {
if (index < array.length) {
const value = array[index] + 1; // Increment each number by 1
index++;
return { value, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}

// Use the custom iterator
const myArray = [1, 2, 3, 4, 5];
const iterator = createCustomIterator(myArray);

let result = iterator.next();
while (!result.done) {
console.log(result.value);
result = iterator.next();
}

Create Iterable Protocol

  • To make an object iterable using the for...of loop, you need to implement the iterable protocol by defining a [Symbol.iterator] method.
const customIterable = {
array: [1, 2, 3, 4, 5],
[Symbol.iterator]: function() {
let index = 0;
let array = this.array;

return {
next: function() {
if (index < array.length) {
const value = array[index] + 1;
index++;
return { value, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};

// Use the custom iterable with a for...of loop
for (let value of customIterable) {
console.log(value);
}

Summary

  • In JavaScript, both generators and iterators handle sequences of values, but they have different purposes and functionalities.
  • Generator is a function that produces or yields a sequence of values using the yield statement. You can think of it as a function that can be paused and resumed.
  • Iterator is an object that provides a mechanism for traversing a collection of data, one element at a time.
  • Iterable is an object that has [Symbol.iterator] method. This method must return an iterator object.

References