JS Generators

Generators are probably one of the lesser known, and most complex features of ES2015.


What is a JS Generator anyway?

The basic idea is actually simple: Stop/pause a function execution at a specific point.


How do I use them ?

Simple.

Define a function with an *. Kinda like a pointers but not really.
Inside the function, use the keyword yeild where you want the generator to be able to pause/stop.
Call next() to move the generator to next step/yield. (kinda like a music playlist)

Here’s an example.


////this is a Generator function.
//notice the *, that's how you define it
function *generator(){

  //you then use the yield keyword to pause it
  yield "first stop";

  //another pause
  yield "second stop";

  //can be null too
  yield;

}

This is how you use it.

//1. To use a generator you need something to control it.
// We call that an iterator.

//this is not a normal function call, you're not calling anything, you generating something you can then control, hence the name.
var iterator = generator();

// 2. then when you call .next, it will move on to next pause point, until it finishes
console.log( iterator.next() ); //logs Object {value: "first stop", done: false}
iterator.next(); //Object {value: "first stop", done: false}
iterator.next(); //Object {value: undefined, done: false}

As you can see, to use a generator, you define it, then create what we call an iterator (a variable, really), to control that generator.

As a plus, since we use an iterator to control the generator we can use ES2015’s new ´for of´ to make this task even easier:

for (let it2 of generator()) {
    //notice
    //1. we don't have to call .next()
    //2. loop stops automatically once it's done
    console.log( it2 );
}


What’s the point?

Simple. This opens up a whole new slew of capabilities.
One of the best is: To handle asynchronous stuff in a synchronous looking manner.

The trick lies in what yield can do for you.
It doesn’t simply pause the function execution.

The yeild can also pass and receive values from our generator. Like a messenger between the generator and w/e is using it.

Here’s an (stupid) example:

function *playlist() {

  let scores = [];

  console.log(scores) //logs []
  let score = yield "Black rain";
  scores.push( score );

  console.log(scores) //logs [3]
  scores.push(yield "Trap door");

  console.log(scores) //[3, 5]

}

let it = playlist();

it.next(1); //Object {value: "Black rain", done: false}
it.next(3); //Object {value: "Trap door", done: false}
it.next(5); //Object {value: undefined, done: true}

See what’s happening?

I’m getting the values from the yields, like the first example.
I’m passing values into the generator, like this .next(3).

If you notice the next(1), the 1 I passed was ignored because there was no yield to receive it, it was the first pause/stop.

The execution behavior may seem a little strange. That’s because it is.
That’s how generators behave.

You can also nest generators inside each other using yield.

Now, all of this is still synchronous, but with this few capabilities we can start to go async.


Where is the Asynchronous

Basically, we can use the generator to pause on a async operation and then resume, via next() at the end of that operation.

Also, do you know about JS promises already? If not, you there is not much to gain by learning about generators before that.

Here’s an example:

//our async fn, it could be for ex an ajax request
function asyncSum( num1, num2, cb ) {
    setTimeout( () => {
        cb( num1 + num2 );
    }, 1000);
}

//a wrapper to abstract the callback away from the generator
function doSum( num1, num2 ) {
    asyncSum( num1, num2, function( result ) {
        it.next( result )
    });
}

function *sumGenerator() {
    let sum1 = yield doSum(1,1);
  	console.log( sum1 );  //logs 2 after 1000ms
    let sum2 = yield doSum(5,5);
  	console.log( sum2 ); //logs 10 after 2000ms
}

let it = sumGenerator();

//start our generator
it.next();

//1000sec later -> logs 2
//2000sec later -> logs 10

Even though our sumGenerator looks synchronous, and very simple, it is actually performing an asynchronous task.
Our doSum function is being called by the generator, which then pauses.
Once our doSum’s callback is fired it calls .next() passing the result back into the generator which keeps resumes it’s execution.

This does look nicer then nested callbacks. But is not that useful.

All we’re doing is turning asynchronous code into synchronous-esque code by pausing our generator when starting an async task and only resuming once the that task is finshed.

We don’t have a lot of flexibility like this, since we can’t easily do error handling, we don’t have control over the generator once it starts and we don’t have an easy way of actually making two asynchronous operations at once, because the generator pauses at each one.

But, aligned with promises, they can make some really great thing make JS a lot more asynchronous friendly.

Here’s where thing get powerful and interesting. And complex.


Generators and Promises

The idea in again, simple.
Just yield out a promise.

We do that with two simples changes:

function asyncSum( num1, num2, cb ) {
    setTimeout( () => {
        cb( num1 + num2 );
    }, 1000);
}

//tell our wrapper to return a promise
function doSum( num1, num2 ) {
    ////CHANGE NUMBER 1
    return new Promise( ( resolve, reject ) => {
        asyncSum( num1, num2, resolve );
    })
}

function *sumGenerator() {
    let sum1 = yield doSum(1,1);
  	console.log( sum1 );  //logs 2 after 1000ms
    let sum2 = yield doSum(5,5);
  	console.log( sum2 ); //logs 10 after 2000ms
}

let it = sumGenerator();

//start our generator, and now we have a promise yielded out
let p = it.next();

////CHANGE NUMBER 2
//that will resolve once the async code is done
p.value.then( ( result ) => {
    let p2 = it.next( result ).value;

    p2.then( (result2) => {
        it.next( result2 )
    })
 })

That’s it, using promises, we have now control of our generator back.

However as you might have noticed, this will quickly get messy, especially in cases where you have multiple yields and have to do error handling.

No to mention, in some of the cases for asynchronous code, like ajax requests, you might want to run the some asynchronous tasks without stopping for each other to finish, as they might be independent.

So, to make this work properly we need some setup/boilerplate code.
This where thing get a little complex.

Here’s one o the many ways we could setup a helper function to do this for us:

function asyncSum( num1, num2, cb ) {
    setTimeout( () => {
        cb( num1 + num2 );
    }, 1000);
}

//a wrapper to abstract the callback away from the generator
function doSum( num1, num2 ) {
    return new Promise( ( resolve, reject ) => {
        asyncSum( num1, num2, resolve );
    })
}

function *sumGenerator() {
    let sum1 = yield doSum(1,1);
    console.log(sum1);
    let sum2 = yield doSum(5,5);
    console.log(sum2);
}

function startGen( gen ) {
    let it = gen();
    let res;

    // return a promise for the generator to complete
    return Promise.resolve()
        .then( function goNext( val ){
            res = it.next( val );

            return (function onRes( res ){
                // generator is finished ?
                if ( res.done )
                    return res.value;
                else {
                    return Promise.resolve( res.value ).then(
                            goNext, //go to the next yield once this promise is resolved
                             // if the promise was rejected (an error occurred on the task), throw the erroe on the iterator
                            (err) => {
                                return Promise.resolve(
                                    it.throw( err )
                                )
                                .then( onRes );    
                            }
                        );
                }
            })(res);
        } );
}

let p = startGen( sumGenerator );
// p is a promise that will fulfill once the generator completes

As you can see, this is neither obvious or simple, you need some time to figure out what it’s doing.
Basically, it is trying to run the a generator to completion, while checking if a yielded promise failed, if so, pass it up to the generator so he can handle it.

Of course this is also a minimal example, real world scenarios will need a lot more wiring to handle more complex cases, and if you’re like me, no way you want to have to deal with that.

Luckily, like everything in the JS world, there’s a library for that. ( asynquence / Q )

And, ultimately ES7 is going to have another feature , built on top of this one that abstracts all of this for you and make the whole thing better. With async and await keywords.
Which works just like it does in C#, if you are familiar with it.


Why should I care?

Honestly? I would risk say, not a whole lot.

As I see it, Generators are a lower level pattern, used a building block for higher, and easier patterns.
The 99% of us will be using an abstraction of it. Either with an helper library or with ES7’s async & await, which abstracts all this boilerplate and make this whole thing a lot easier to manage.




Go back.