Coroutines

Let’s get started

If you are working with JavaScript, there’s a good chance that you have a ton of promises or callbacks nested over and over again. Promises helped me clean up the numerous callbacks, but coroutines really took it to the next level. Coroutines allow you to remove callbacks entirely, and write asynchronous code that looks completely synchronous. In a couple of quick steps, I’ll show you how to simplify your promise-based code by converting to coroutines.

Note: This article briefly talks about generators. If you would like a more thorough description, check out my article on generators!

Let’s start with an example

Here’s an example using only promises. I’ve made it a little complex to really show off how powerful coroutines are. Throughout the rest of this post I’ll walk you through the conversion process.

Note: The request method performs an HTTP GET request on a url, and returns a Promise.

Example 1

function GET() {
    // Make an HTTP GET request to http://www.dashron.com
    return request('http://www.dashron.com')
        .then(function (json) {
            // parse the response
            json = JSON.parse(json);
            // Request a couple more web pages in response to the first request
            return Promise.resolveAll([
                request('http://www.dashron.com/' + json.urls[0]),
                request('http://www.dashron.com/' + json.urls[1])
            ])
            .then(function (pages) {
                // Build the response object
                return {
                    main: json,
                    one: pages[0],
                    two: pages[2]
                };
            });
        })
        .catch(function (error) {
            // Handle errors
            console.log(error);
        });
}

// When the GET request is complete, log the response, which is a combination of all responses
GET().then(function (response) {
    console.log(response);
});

Create a coroutine

First you need to create a coroutine. I’ve written a library (roads-coroutine) that helps you build coroutines. This library exposes a function, which takes a generator function as its only parameter and returns a coroutine.

Example 2

var coroutine = require('roads-coroutine');

var GET = coroutine(function* GET() {
    // ... Removed for brevity
});

GET().then(function (response) {
    console.log(response);
});

Find your promises, and add yield statements

Find all your promises, and throw yield directly in front (without removing anything!). yield is a keyword that can only be used in generators. In the below example, it was added before request and Promise.resolveAll. When everything is done, yield acts like an asynchronous equals sign. It will wait until the promise is resolved, and pass the result to the left. If the promise is rejected, yield will throw the appropriate error.

Example 3

var coroutine = require('roads-coroutine');

var GET = coroutine(function* GET() {
    // Make an HTTP GET request to http://www.dashron.com
    return yield request('http://www.dashron.com')
    .then(function (json) {
        // parse the response
        json = JSON.parse(json);
        // Request a couple more web pages in response to the first request
        return yield Promise.resolveAll([
            request('http://www.dashron.com/' + json.urls[0]),
            request('http://www.dashron.com/' + json.urls[1])
        ])
        .then(function (pages) {
            // Build the response object
            return {
                main: json,
                one: pages[0],
                two: pages[2]
            };
        });
    })
    .catch(function (error) {
        // Handle errors
        console.log(error);
    });
});

// When the GET request is complete, log the response, which is a combination of all responses
GET().then(function (response) {
    console.log(response);
});

Kill your promise handlers

Now that yield handles your promise functions for you (by returning the result, and throwing the reject), you can just use normal variables and try/catch. In example 4 I remove all then and catch statements, and replace them with variables and a try/catch.

Example 4

var GET = coroutine(function* GET() {
    try {
        // Make an HTTP GET request to http://www.dashron.com
        var json = yield request('http://www.dashron.com');

        // parse the response
        json = JSON.parse(json);
        // Request a couple more web pages in response to the first request
        var pages = yield [
            request('http://www.dashron.com/' + json.urls[0]),
            request('http://www.dashron.com/' + json.urls[1])
        ];

        return {
            main: json,
            one: pages[0],
            two: pages[2]
        };
    } catch (error) {
        // Handle errors
        console.log(error);
    };
});

// When the GET request is complete, log the response, which is a combination of all responses
GET().then(function (response) {
    console.log(response);
});

Notice all nesting is gone. Instead of making more requests in the then of the first promise, yield will handle the waiting for you. The above code looks synchronous, and is much easier to read.

Aesthetics aside, this solves one major headache with promises. With promises, if you ever forget a catch, your error will be ignored, and lost forever (or caught by the hard-to-manage uncaughtRejectionHandler. With coroutines, your exceptions will be thrown as expected, and can be processed as you see fit.

Let’s make it official

ECMAScript 7 is adding two new keywords to support this feature natively. async functions instead of generators, and the await keyword instead of yield.

async function () {
    console.log(await request("http://dashron.com"));
}

There are some minor differences which make this system better (such as order of operations) but we have a while until it will be available for use. In the meanwhile, keep using roads-coroutine!

Posted toTechon5/17/2016