Skip to content
On this page

Generator

A generator is a new data type introduced by the ES6 standard. A generator looks like a function but can return multiple times.

The standard for generators in ES6 borrowed concepts and syntax from Python's generator. If you are familiar with Python's generator, then ES6's generator will be a piece of cake. If you're not familiar with Python, you should quickly catch up on Python tutorials!

Let's first review the concept of functions. A function is a complete piece of code, and calling a function means passing in parameters and returning results:

javascript
function foo(x) {
    return x + x;
}

let r = foo(1); // Calling foo function

During execution, if a function does not encounter a return statement (if there’s no return at the end of the function, it implicitly returns undefined), control cannot return to the calling code.

Generators are similar to functions but are defined as follows:

javascript
function* foo(x) {
    yield x + 1;
    yield x + 2;
    return x + 3;
}

The difference between generators and functions is that generators are defined with function* (note the asterisk), and in addition to the return statement, they can use yield to return multiple times.

Most students might get confused right away: a generator is just a "function" that can return multiple times? What's the use of returning multiple times?

Let’s illustrate with an example.

We can use the famous Fibonacci sequence, which starts with 0 and 1:

0 1 1 2 3 5 8 13 21 34 ...

To create a function that produces the Fibonacci sequence, you can write it like this:

javascript
function fib(max) {
    let t,
        a = 0,
        b = 1,
        arr = [0, 1];
    while (arr.length < max) {
        [a, b] = [b, a + b];
        arr.push(b);
    }
    return arr;
}

// Test:
fib(5); // [0, 1, 1, 2, 3]
fib(10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Since a function can only return once, it must return an array. However, if we switch to using a generator, it can return one number at a time, continuously returning multiple times. Here’s how to rewrite it with a generator:

javascript
function* fib(max) {
    let t,
        a = 0,
        b = 1,
        n = 0;
    while (n < max) {
        yield a;
        [a, b] = [b, a + b];
        n++;
    }
    return;
}

Let’s try calling it directly:

javascript
fib(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}

Calling a generator is different from calling a function; fib(5) merely creates a generator object without executing it.

There are two ways to call a generator object: one is to continually call the generator object's next() method:

javascript
let f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}

The next() method will execute the generator's code, and each time it encounters yield x;, it returns an object {value: x, done: true/false} and then "pauses." The returned value is the result of the yield, and done indicates whether the generator has finished executing. If done is true, then value is the return value.

When the execution reaches done: true, the generator object has completed execution, and you should not call next() anymore.

The second method is to iterate over the generator object directly using a for ... of loop, which does not require us to manually check for done:

javascript
function* fib(max) {
    let a = 0,
        b = 1,
        n = 0;
    while (n < max) {
        yield a;
        [a, b] = [b, a + b];
        n++;
    }
    return;
}

for (let x of fib(10)) {
    console.log(x); // Outputs 0, 1, 1, 2, 3, ...
}

What are the advantages of generators compared to ordinary functions?

Since generators can return multiple times during execution, they resemble functions that can remember their execution state. By utilizing this, you can achieve functionalities that would typically require object-oriented programming. For example, to maintain state using an object, you would write:

javascript
let fib = {
    a: 0,
    b: 1,
    n: 0,
    max: 5,
    next: function () {
        let r = this.a,
            t = this.a + this.b;
        this.a = this.b;
        this.b = t;
        if (this.n < this.max) {
            this.n++;
            return r;
        } else {
            return undefined;
        }
    }
};

Using object properties to store state is quite cumbersome.

Another significant advantage of generators is that they can turn asynchronous callback code into "synchronous" code. You'll appreciate this benefit once you learn about AJAX later.

In the dark ages before generators, using AJAX required writing code like this:

javascript
ajax('http://url-1', data1, function (err, result) {
    if (err) {
        return handle(err);
    }
    ajax('http://url-2', data2, function (err, result) {
        if (err) {
            return handle(err);
        }
        ajax('http://url-3', data3, function (err, result) {
            if (err) {
                return handle(err);
            }
            return success(result);
        });
    });
});

The more callbacks, the harder the code becomes to read.

With the advent of generators, you can write AJAX code like this:

javascript
try {
    r1 = yield ajax('http://url-1', data1);
    r2 = yield ajax('http://url-2', data2);
    r3 = yield ajax('http://url-3', data3);
    success(r3);
} catch (err) {
    handle(err);
}

It looks like synchronous code but is actually executed asynchronously.

Exercise

To generate an incrementing ID, you can write a next_id() function:

javascript
let current_id = 0;

function next_id() {
    current_id++;
    return current_id;
}

Since functions cannot maintain state, a global variable current_id is needed to hold the number.

Using a generator instead, rewrite it as follows:

javascript
function* next_id() {
    ???
}

// Test:
let x,
    pass = true,
    g = next_id();
for (x = 1; x < 100; x++) {
    if (g.next().value !== x) {
        pass = false;
        console.log('Test failed!');
        break;
    }
}
if (pass) {
    console.log('Test passed!');
}
Generator has loaded