Close

JavaScript - ES6 Generator Functions

[Updated: Nov 23, 2018, Created: Nov 14, 2018]

A generator function is a special kind of iterator which can return the iteration values by using yield keyword multiple times. Let's understand that in steps.

Creating a generator function

A generator function can be declared by using * after function keyword:

function* myGenerator (){}

Calling a generator function

Calling a generator function does not actually execute the body but an instance of iterator object is returned. As per Iterator Protocol this object has a next() method:

function* myGenerator() {}
let myGen = myGenerator();
let s = myGen.next();
console.log(s);

Calling next() method first time executes the generator's body. As we are not returning anything from our generator function via yield yet, it finishes immediately with done=true.

Using keyword yield

We can think of yield keyword as similar to return keyword, both return values, but yield can be used multiple times and whenever yield is used, it returns a value and pauses until another next() call is made.

function* myGenerator() {
    yield 3;
    console.log("generator function ends");
}
let myGen = myGenerator();
let s = myGen.next();
console.log(s);
s = myGen.next();
console.log(s);

If we do not call next() enough number of times, till the last yield statement execution, the generator method will never complete:

function* myGenerator() {
    yield 3;
    console.log("generator function ends");
}
let myGen = myGenerator();
let s = myGen.next();
console.log(s);

The next() and yield collaboration

When first time next() is called, the generator function will start executing. It will return the first value on encountering the first yield statement. After that, the generator function execution will pause and will wait for the subsequent next() call. On the second next() call, the execution will resume till another yield is encountered, so this goes on till the end of the function where done=true is returned:

function* myGenerator() {
    console.log('func: generator function starts');
    console.log("func: yielding 1st time");
    yield 3;
    console.log('func: generator function resumes 1st time');
    console.log("func: yielding 2nd time");
    yield 5;
    console.log('func: generator function resumes 2nd time');
    console.log('func: no more yielding, generator function ends'); //it will return done=true
}
let myGen = myGenerator();
console.log("calling next() 1st time");
let s = myGen.next();
console.log("returned: %o\n", s);
console.log("calling next() 2nd time");
s = myGen.next();
console.log("returned: %o\n", s);
console.log("calling next() 3rd time");
s = myGen.next();
console.log("returned: %o\n", s);

Using for-of loop

Since a generator function returns an instance of iterator object, it can be used in for-of loop;

function* myGenerator() {
    yield 3;
    yield 5;
    yield 7;
}
let myGen = myGenerator();
for (let v of myGen) {
    console.log(v);
}

Passing arguments

We can also pass arguments to the generator functions.

In following example we will create a generator function which will return even numbers for the provided range of integers:

function* getEvenNumbers(start, end) {
    for (let i = start; i <= end; i++) {
        if (i % 2 == 0) {
            yield i;
        }
    }
}

//using for-of loop
let evenNumbers = getEvenNumbers(3, 9);
for (let n of evenNumbers) {
    console.log(n);
}

//using next() method
evenNumbers = getEvenNumbers(3, 9);
let temp = { done: false }
while (!temp.done) {
    console.log(temp = evenNumbers.next());
}

See Also