JavaScript Iterators and Iterables


JavaScript provides a powerful way to traverse collections of data with the help of iterators and iterables. These concepts are fundamental when working with loops, arrays, or even custom objects that you want to iterate over.

What are Iterators in JavaScript?

An iterator is an object that enables you to iterate over a collection of data, such as an array, string, or even a custom object. It provides a standardized way to access the elements of an iterable one by one.

An iterator object must implement the following methods:

  • next(): Returns the next item in the collection in the form of an object { value, done }:
    • value: The next item in the collection.
    • done: A boolean that indicates whether the collection has been fully traversed (true if all items have been consumed).

Example: Basic Iterator

const numbers = [1, 2, 3];
const iterator = numbers[Symbol.iterator]();

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

Here, we manually create an iterator for the array numbers by calling Symbol.iterator and use next() to get each value one by one. The done property indicates when the iteration has finished.

What are Iterables in JavaScript?

An iterable is any object that can be iterated over, meaning you can use the for...of loop or other iterator-based methods to traverse its elements. For an object to be iterable, it must implement the Symbol.iterator method, which returns an iterator.

Built-in iterables in JavaScript include:

  • Arrays
  • Strings
  • Maps
  • Sets

Example: Iterable with for...of

const fruits = ["apple", "banana", "cherry"];

for (let fruit of fruits) {
  console.log(fruit);
}
// Output:
// apple
// banana
// cherry

The for...of loop automatically uses the iterable's Symbol.iterator method under the hood to get the next item until the iteration is complete.

Difference Between Iterators and Iterables

While the terms iterator and iterable are related, they are different concepts.

Feature Iterable Iterator
Definition An object that can be iterated over (it has a Symbol.iterator method). An object that provides the next() method to traverse through data.
Example Arrays, Strings, Maps, Sets The object returned by Symbol.iterator
Access Method Accessed via for...of, spread syntax, or Array.from() Accessed via next() method.
Property Must have a Symbol.iterator method. Must have a next() method.

In short:

  • An iterable is an object that implements the Symbol.iterator method, allowing it to be used in for...of loops and other iterable-based operations.
  • An iterator is an object that provides the next() method, enabling you to retrieve the next item in the iterable.

Creating Custom Iterables

You can create your own iterables by defining the Symbol.iterator method on an object. This allows you to control how an object is iterated.

Example: Custom Iterable Object

const myIterable = {
  data: ["JavaScript", "Python", "Ruby"],
  [Symbol.iterator]: function() {
    let index = 0;
    const data = this.data;
    return {
      next: function() {
        if (index < data.length) {
          return { value: data[index++], done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

for (let language of myIterable) {
  console.log(language);
}
// Output:
// JavaScript
// Python
// Ruby

In this example:

  • We define a custom object myIterable with a Symbol.iterator method.
  • The next() method is used to return the next item from the data array.

Using Built-In Iterables in JavaScript

JavaScript provides several built-in iterables such as Array, String, Map, and Set. You can directly use these in for...of loops or convert them to arrays using methods like Array.from().

Example: Iterating over a String (Iterable)

const message = "Hello, World!";

for (let char of message) {
  console.log(char);
}
// Output:
// H
// e
// l
// l
// o
// ,
//  (space)
// W
// o
// r
// l
// d
// !

In this case, the string "Hello, World!" is an iterable, and we can use for...of to loop through each character.

Example: Using Array.from() with Iterables

const numbers = new Set([1, 2, 3, 4]);
const arr = Array.from(numbers);

console.log(arr);  // [1, 2, 3, 4]

In this case, we use Array.from() to convert a Set (which is an iterable) into an array.

Generators: A Special Type of Iterable

Generators are a special type of iterable in JavaScript. They allow you to define custom iterators using the function* syntax. Generators are often used when you want to create a sequence of values on demand (lazily).

Example: Using a Generator Function

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

const gen = generateNumbers();

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

Here:

  • The function* syntax defines a generator function.
  • The yield keyword is used to return values one at a time.
  • next() is used to get each yielded value from the generator.

Practical Use Cases for Iterators and Iterables

1. Lazy Evaluation with Generators

Generators are ideal when you want to generate values lazily, meaning values are produced only when needed, which can help with performance when dealing with large data sets.

function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

const gen = fibonacci();

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

This generator produces Fibonacci numbers on demand.

2. Custom Data Structures

You can create custom data structures (like linked lists, trees, or queues) that implement the iterable protocol, allowing them to be used in for...of loops and other iterable operations.

3. Asynchronous Iterators

JavaScript also supports asynchronous iterators using for-await-of, allowing you to iterate over async data, like data from an API, asynchronously.

async function* asyncGenerator() {
  yield "first";
  yield "second";
  yield "third";
}

(async () => {
  for await (let value of asyncGenerator()) {
    console.log(value);
  }
})();

In this example, for-await-of is used to handle asynchronous data with a generator function.