Generators

(psssst! All the code referred to in this post can be found here, just check out the generators.js file.)

Generators are kind of interesting. They're a new kind of function/object thing. They've got this weird yield keyword (in addition to the return keyword) that pauses function execution and returns a value. Call the function again, and execution starts after the yeild expression until it hits another yield, a return or the end of the function (returning undefined if no return statement is present).

Here's an example:

const  
    R = require('ramda')

//Simple generator example
const  
    starFunction = function* () {
        yield 'thing'
    },
    gen1 = starFunction()

R.range(0, 2).forEach(() => console.log(gen1.next()))  
/*
    prints:
    { value: 'thing', done: false }
    { value: undefined, done: true }
*/

See how I had to define starFunction and then gen1? That's what I mean by 'weird object thing'. gen.next() doesn't even return what is yielded. Instead we get an object that contains the value yielded, and a flag showing if the generator is done or not.

Pretty odd right? And you can do as many of these yield thingies as you want:

//You can yield more than once in a generator
const  
    gen2 = (function* () {
        yield 'you'
        yield 'are'
        yield 'a'
        yield 'potato'
    })(),
    runGen = (n, gen) =>
        R.range(0, n)
            .forEach(() =>
                console.log(gen.next().value))

runGen(4, gen2)  
/*
    prints:
    you
    are
    a
    potato
*/

You can use loops:

//we can use while and for loops here too
const  
    makeGenerator = starFunction => starFunction(),
    gen3 = makeGenerator(function* () {
        var i = 0
        while(i < 3) yield i++
    })

    runGen(5, gen3)
/*
    prints:
    0
    1
    2
    undefined
    undefined
*/

Which can go on forever if you like:

//In fact, we can loop a generator forever
const  
    gen4 = makeGenerator(function* () {
        var i = 0
        while(true) yield i++
    })

    runGen(5, gen4)
/*
    prints:
    0
    1
    2
    3
    4
*/

There's also this yield* keyword that defers to other generators, or other iterable things like arrays.

Check it oooooout:

const  
    gen5 = makeGenerator(function* () {
        yield 'here\'s a list'
        yield* ['this', 'is', 'in', 'the', 'list']
        yield 'here\'s stuff from another generator'
        yield* (function* () {
            var i = 0; while(i < 3) yield i++ })()
        yield 'this next one goes on forever'
        yield* (function* () {
            var i = 0; while(true) yield i++ })()
    })

    runGen(15, gen5)
/*
    prints:
    here's a list
    this
    is
    in
    the
    list
    here's stuff from another generator
    0
    1
    2
    this next one goes on forever
    0
    1
    2
    3*/

You can also pass parameters to your star function affecting the resulting generator:

//Here's an interesting thing you can do with generators
const  
    multGenerator = function* (y) {
       yield y * y
    },
    add3Generator = function* (a, b, c) {
        yield a + b + c
    }
console.log(multGenerator(2).next().value)  
console.log(multGenerator(4).next().value)  
console.log(add3Generator(1, 2, 3).next().value)  
console.log(add3Generator(4, 5, 6).next().value)  
/*
prints  
4  
16  
6  
15  
*/

And you can pass stuff to next:

//You can also pass stuff to next
const  
    usesNext = makeGenerator(function* () {
        console.log('start')
        while(true) console.log(yield)
    })

usesNext.next()  
usesNext.next('stuff')  
usesNext.next('things')  
/*
prints  
start  
stuff  
things  
*/

Weird huh? There's more stuff you can do with them, but the thing about them that really interests me is how they interact with promises. More about that very soon.

Looking for a software developer?