Introduction to JavaScript Iterators and Generators
In JavaScript, an iterator is an object that defines a sequence and a return value for that sequence. It allows you to iterate over a collection of values, such as an array or an object, and perform a specific operation on each value. Iterators are a powerful feature of the language that can greatly simplify the process of working with collections of data.
A generator is a special type of function that can be used to create an iterator. It allows you to define a sequence of values and pause the execution of the function at any point, returning a value and saving the state of the function. This makes generators a useful tool for implementing iterators and creating sequences of data that can be iterated over.
In this article, we will explore the concepts of iterators and generators in JavaScript and discuss how they can be used in your code.
How Iterators work?
The for/of loop and spread operator work seamlessly with iterable objects, but it is worth understanding what is actually happening to make the iteration work. There are three separate types that you need to understand to understand iteration in JavaScript.
- First, there are the iterable objects: these are types like Array, Set, and Map that can be iterated.
- Second, there is the iterator object itself, which performs the iteration.
- And third, there is the iteration result object that holds the result of each step of the iteration.
An iterator has two main methods: next()
and return()
. The next()
method returns an object with two properties: value
and done
. The value
property is the next value in the sequence, and the done
property is a boolean that indicates if the iterator has reached the end of the sequence. When the iterator is finished, the done
property will be true
and the value
property will be undefined
.
Here is an example of a simple iterator that iterates over an array of numbers:
const numbers = [1, 2, 3, 4];
const numbersIterator = {
index: 0,
next: function () {
if (this.index < numbers.length) {
return { value: numbers[this.index++], done: false };
} else {
return { done: true };
}
},
};
To use this iterator, we can call the next()
method repeatedly until the done
property is true
.
let result = numbersIterator.next();
while (!result.done) {
console.log(result.value);
result = numbersIterator.next();
}
Output
1
2
3
4
This will output the numbers 1 through 4 to the console.
How Generator works?
A generator is a special type of function that can be paused and resumed. When a generator function is called, it does not execute the function body immediately. Instead, it returns a generator object that can be used to execute the function body.
A generator function is defined using the function*
syntax. When you invoke a generator function, it does not actually execute the function
body, but instead returns a generator
object. This generator object is an iterator. Calling its next()
 method causes the body of the generator function to run from the start (or whatever its current position is) until it reaches a yield
 statement.
yield
 is new in ES6 and is something like a return
 statement. The value of the yield
 statement becomes the value returned by the next()
 call on the iterator.
Here is an example of a generator function that yields the numbers 1
through 5
:
function* numbersGenerator() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
const numbers = numbersGenerator();
To execute the generator function, we can call the next()
method on the generator object.
console.log(numbers.next().value);
console.log(numbers.next().value);
console.log(numbers.next().value);
Output
1
2
3
Example-1: Accept arguments with Generators
Generators can also accept arguments and return values using the return()
method.
function* addGenerator(x) {
const y = yield;
return x + y;
}
const add = addGenerator(5);
console.log(add.next());
console.log(add.next(7));
Output
{ value: undefined, done: false }
{ value: 12, done: true }
Example-2: Using waitForPromise with Generators
Generators can be used to simplify asynchronous code by allowing the function to pause and wait for a promise to resolve. For example, here is a generator function that waits for a promise to resolve before continuing execution
function* waitForPromise(promise) {
const result = yield promise;
return result;
}
const wait = waitForPromise(Promise.resolve(10));
wait.next().value.then((result) => console.log(wait.next(result).value));
Output
10
Example-3: Create infinite sequences
Generators can also be used to create infinite sequences using the yield*
keyword. The yield*
keyword allows the generator to delegate to another generator or iterable object.
Here is an example of a generator that yields an infinite sequence of numbers
function* countGenerator() {
let i = 0;
while (true) {
yield i++;
}
}
const count = countGenerator();
console.log(count.next().value);
console.log(count.next().value);
console.log(count.next().value);
Output
0
1
2
Example-4: Create custom iterators
Generators can be used to create custom iterators, as they have a built-in next()
method. Here is an example of a generator that acts as an iterator for an array
function* arrayIterator(array) {
for (const value of array) {
yield value;
}
}
const iterator = arrayIterator([1, 2, 3]);
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);
Output
1
2
3
Example-5: Return value of Generator function
The return value of the next()
 function is an object that has a value
 property and/or a done property. With typical iterators and generators, if the value
 property is defined, then the done property is undefined or is false
. And if done is true
, then value
 is undefined. But in the case of a generator that returns a value, the final call to next
 returns an object that has both value
 and done
 defined. The value
 property holds the return value of the generator function, and the done
 property is true
, indicating that there are no more values to iterate. This final value is ignored by the for/of loop and by the spread operator, but it is available to code that manually iterates with explicit calls to next()
:
function *oneAndDone() {
yield 1;
return "done";
}
// The return value does not appear in normal iteration.
[...oneAndDone()] // => [1]
// But it is available if you explicitly call next()
let generator = oneAndDone();
generator.next() // => { value: 1, done: false}
generator.next() // => { value: "done", done: true }
// If the generator is already done, the return value is not returned again
generator.next() // => { value: undefined, done: true }
Summary
In summary, iterators and generators are useful tools in JavaScript for creating and iterating over sequences of values. They can be used to simplify asynchronous code and create custom iterators.
Let's summarise what we have learned:
- An iterator object has aÂ
next()
 method that returns an iteration result object. - An iteration result object has aÂ
value
 property that holds the next iterated value, if there is one. If the iteration has completed, then the result object must have aÂdone
 property set toÂtrue
. - Generator functions (functions defined withÂ
function*
 instead of function) are another way to define iterators. - When you invoke a generator function, the body of the function does not run right away; instead, the return value is an iterable iterator object. Each time theÂ
next()
 method of the iterator is called, another chunk of the generator function runs. - Generator functions can use theÂ
yield
 operator to specify the values that are returned by the iterator. Each call toÂnext()
 causes the generator function to run up to the next yield expression. - The value of thatÂ
yield
 expression then becomes the value returned by the iterator. When there are no moreÂyield
 expressions, then the generator function returns, and the iteration is complete.
References
Iterators and generators - JavaScript | MDN (mozilla.org)