Generators And Promises

(psst! All the code referred to in this post can be found here! Just have a look at front-end/js/main.js)

Ok, this topic took me a bit of time to figure out, but it all seemed pretty simple once I did. Here's the deal:

You can use generators to make using promises seem a lot more like writing synchronous code.

So here's how you might use a promise normally:

fetch(  
    '/ajax',
    {
        method: 'post',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            param: 'You are a penguin' })
        })
    .then(response => response.json())
    .then(console.log)

fetch returns a promise, so does response.json(), lastly we print the result to the console.

This result happens to be:

{message: "the param is You are a penguin"}

Ok, so here's how we'd do it with generators:

async(function* () {  
    const
        response = yield fetch(
            '/ajax',
            {
                method: 'post',
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    param: 'Bananas are my friends' })
                }),
        json = yield response.json()
    console.log(json)
})

And the console prints:

{message: "the param is Bananas are my friends"}

In this generator approach, I'm passing a star function to a function called async, the guts of which I'll get to in a moment.

But have a look at the innards of this star function first. The first yield yields the asynchronous result of calling fetch, not the promise it returns.

response doesn't hold the promise, it holds the resolved async value promise represented.

async waits for the result of fetch before giving it back to the start function and moving on. It does the same thing with response.json. It'll keep returning resolved async values till the cows come home.

So now, inside this star function at least, our code looks much more familiar. No messing about with .then.

How does async work? Pretty simply:

const  
    async = starFunc => {
        const
            iterator = starFunc(),
            handle = x => {
                const iteration = iterator.next(x)
                if(!iteration.done) {
                    iteration.value.then(handle)
                }
            }
        iterator.next().value.then(handle)
    }

async takes the star function, creates a generator with it, and gets the first yielded promise. Then calls it's .then with handle.

handle puts the promises resolved value in the iterator's next method, handing that value back into the star function. If the iterator isn't done yet, handle is recursively called for the next next call until the iterator is done.

Pretty sweet right?

Well, there is one little issue. I've not been handling errors at all in the above code examples. I could go on to write some code for you that did, but I'd rather not. I think you get the basic idea for how generators and promises work together.

Besides, someone already wrote a more robust version of my piddly little async. It's called co:

const  
    co = require('co')

co(function* () {  
    const
        response = yield fetch(
            '/ajax',
            {
                method: 'post',
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    param: 'Your mom is a potato' })
                }),
        json = yield response.json()
    console.log(json)
})

Result?

{message: "the param is Your mom is a potato"}

co works just like async does, but it also returns a promise so you can .catch the whole deal:

co(function* () {  
    const
        response = yield fetch(
            '/banana',
            {
                method: 'post',
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    param: 'Your mom is a potato' })
                }),
        json = yield response.json()
    console.log(json)
})
.catch(console.log)

Here we log the error:

SyntaxError: Unexpected token C in JSON at position 0  

Because the path /banana doesn't exist and we don't get a proper json blob as a response, .catch gets a parsing error.

This is all pretty cool, but what I'm really excited to talk about next is a redux library that uses the generators/promises approach to handle side-effects and keep the rest of your code purely functional.

It's called redux-saga, and I'll be posting about it soon.

Looking for a software developer?