Generators don't magically make asynchronous code synchronous. All they do is return an iterator which can be used to step through a function (which is what the above code shows). For a real code, we need something that understands and automatically executes iterators. Popular libraries are co, Q and suspend. If you're using Koa (or some other generator-aware framework) this should all be setup for you; your code will already be running within a generator-aware control flow pipeline.

The other thing to understand is that Generators work in conjunction with, not as a replacement for, promises. While this isn't a strict rule, it's how non-trivial examples work. Understanding how the two work together was my aha moment. Specifically, yielding to a promise stops execution until said promise is resolved.

The next step is turning the above function into a generator. In CoffeeScript, a function that yields is a generator. In JavaScript the function would need to be declared with *: function* query(sql, params) {...}.

Before we do the same change to conn.query, let's clean up our promise code by switching from the native ES6 Promise to Bluebird. With bluebird, we can easily create promises for an objects methods via promisifyAll:

The first lines generates an *Async version of every method exposed directly by pg (Async methods return promises). The second line does the same thing for all instance methods of the Client object.

That's kind of all there is to it. You do end up using yield in a lot of places, but it does wipe out all nesting (something we couldn't quite get done with Promises alone). It also brings back more conventional exception handling, which again, never felt 100% right with promises.

To get a better feel for things, here's some code you can play with. It's a bit more standalone.