JS Tip of the Day: Generator Prototypes

Generator Prototypes
Version: ES2015
Level: Advanced

Most functions that can’t be constructors also don’t have prototype properties. Generator functions are different. Though they too can’t be constructors, they still have a prototype.

let arrow = () => {}
console.log(typeof arrow.prototype); // undefined

let obj = { method () {} };
console.log(typeof obj.method.prototype); // undefined

function * gen () {}
console.log(typeof gen.prototype); // object

This is because, while not constructors, they are factory functions that create new generator objects whenever they’re called. The generator objects returned from a generator function will have prototypes set to the prototype of the generator function.

function * gen () {}
let iter = gen();
console.log(Object.getPrototypeOf(iter) === gen.prototype); // true

This provides an opportunity to give your generator objects custom methods by defining them in the generator function’s prototype.

function * countTo (count) {
    for (let i = 1; i <= count; i ++) {
        yield i;
    }
}
countTo.prototype.evens = function () {
    return {
        next: (...args) => {
            let result;
            do {
                result = this.next(...args);
            } while (!result.done && result.value % 2 === 1);
            return result;
        },
        return: this.return.bind(this),
        throw: this.throw.bind(this),
        [Symbol.iterator] () {
            return this;
        }
    };
}
for (let num of countTo(4)) {
    console.log(num); // 1 2 3 4
}
for (let num of countTo(4).evens()) {
    console.log(num); // 2 4
}

Here an evens() method is added to the countTo generator that creates a new iterator/iterable object that reads the next() iterator results from the original generator object and then only provides them as iterator results from itself if the value is even. In addition, it forwards any calls it gets for return() and throw() to the original iterator.

Creating a generator method like evens() can be difficult. The way it works as a wrapper for the original iterator can be awkward, especially in having to make sure methods like return() and throw() are forwarded. Additionally, you’re left out of being able to chain further unless evens() provided additional methods of its own because as an iterator object and not a generator from countTo(), it doesn’t inherit any other methods that may have been defined within countTo.prototype. As such, it’d be uncommon for anyone to make generator methods this way and I wouldn’t recommend you start doing so yourself.

That said, there is a new proposal that adds new methods to iterators that would work much like evens() above. Generator objects would inherently have access to methods like map(), filter(), etc. for modifying the output of the generator results during iteration. For more information see the Iterator Helpers link below.

More info: