Iterators and Generators

Arushi Bajpai
Sep 3, 2018 · 4 min read
“green leafed plants on gray concrete brick wall” by Tim Johnson on Unsplash

The following article is meant to discuss about Iterators, Generators, Iterable objects and Yield keyword as provided in ECMAScript 6. For the in-depth explanation please see MDN.

Iterable Objects

Iterable objects are a generalization of arrays. That’s a concept that allows to make any object usable in a for..of loop.

Arrays by themselves are iterable. But not only arrays. Strings are iterable too, and many other built-in objects as well.

Iterables are widely used by the core JavaScript. As we’ll see many built-in operators and methods rely on them.

Iterating over an Array

let iterable = [10, 20, 30];

for (let value of iterable) {
value += 1;
console.log(value);
}
// 11
// 21
// 31

Built-in iterables

String, Array, TypedArray, Map and Set are all built-in iterables, because each of their prototype objects implements an @@iterator method.

User-defined iterables

We can make our own iterables like:

let range = {
from: 1,
to: 5
};

To make the range iterable (and thus let for..of work) we need to add a method to the object named Symbol.iterator (a special built-in symbol just for that).

range[Symbol.iterator] = function() {

//it should return an iterator object which has a next() function
}
  • When for..of starts, it calls that method (or errors if not found).
  • The method must return an iterator — an object with the method next.
  • When for..of wants the next value, it calls next() on that object.
  • The result of next() must have the form {done: Boolean, value: any}, where done = truemeans that the iteration is finished, otherwise value must be the new value.

Here’s the full implementation for range:

let range = {
from: 1,
to: 5
};
// 1. call to for..of initially calls this
range[Symbol.iterator] = function() {
// 2. ...it returns the iterator:
return {
current: this.from,
last: this.to,
// 3. next() is called on each iteration by the for..of loop
next() {
// 4. it should return the value as an object {done:.., value :...}
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
// now it works!
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}

Note:

The range itself does not have the next() method.

Instead, another object, a so-called “iterator” is created by the call to range[Symbol.iterator](), and it handles the iteration.

Generators

Generators are functions which can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances.

Generator functions allow you to define an iterative algorithm by writing a single function whose execution is not continuous (by using yield keyword)

Generator functions are written using the function* syntax.

Example of a simple generator function:

function* foo(){
yield 1;
yield 2;
}
var gen = foo();
console.log(gen.next()); // {value: 1, done: false}

Calling a generator function does not execute its body immediately; an iterator object for the function is returned instead.

Hence doing following causes error:

>    foo.next(); // Uncaught TypeError: foo.next is not a function

When the iterator’s next() method is called, the generator function's body is executed until the first yield expression, which specifies the value to be returned from the iterator.

The next() method returns an object with a value property containing the yielded value and a done property which indicates whether the generator has yielded its last value as a boolean.

For the above snippet doneproperty results to true after all yields have finished:

>   gen.next()  
// {value: 1, done: false}
> gen.next()
//{value: 2, done: false}
> gen.next()
//{value: undefined, done: true}

Calling the next() method with an argument(if required by generator function) will resume the generator function execution, replacing the yieldexpression where execution was paused with the argument from next().

A return statement in a generator, when executed, will make the generator finished (i.e the done property of the object returned by it will be set to true). If a value is returned, it will be set as the value property of the object returned by the generator.

Read: Generator without yield

Yield

The yield keyword is used to pause and resume a generator function

The yield keyword pauses generator function execution and the value of the expression following the yield keyword is returned to the generator's caller. It can be thought of as a generator-based version of the return keyword.

The yield keyword actually returns an IteratorResult object with two properties, value and done. The value property is the result of evaluating the yield expression, and done is false, indicating that the generator function has not fully completed.

  • A yield, which causes the generator to once again pause and return the generator's new value. The next time next() is called, execution resumes with the statement immediately after the yield.
  • throw is used to throw an exception from the generator. This halts execution of the generator entirely, and execution resumes in the caller as is normally the case when an exception is thrown.
  • The end of the generator function is reached; in this case, execution of the generator ends and an IteratorResult is returned to the caller in which the value is undefined and done is true.
>   gen.next()
//{value: undefined, done: true}
  • A return statement is reached. In this case, execution of the generator ends and an IteratorResult is returned to the caller in which the value is the value specified by the return statement and done is true.

Example : Using Generator to define our user-defined iterables:

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable]; // [1, 2, 3]

[…IteratorObj] is a Spread syntax which uses the same iteration protocol under the hood.

If this was useful, please click the clap 👏 button down below a few times to show your support! ⬇⬇⬇

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade