Edit:: Part 2 of this article - How to flatten an existing JavaScript codebase - now up up!

I started doing JavaScript sometime around 2011. I was server/Python person, and node 0.4 had a lot of cool things: DOM manipulation on the server, HTTP as a first class citizen, and the ability to get a million people to connect to your server at once without it being a big deal.

Like most JS newcomers, I expected to save files, GET stuff from the web, and other async tasks the same way I was used to in Python and have async IO 'just work': Here's me in 2011 expecting exactly that.

Eventually I began understanding the idea of functions as values, that could be written inline, and passed to other functions as options. A callback was essentially a 'what to do next' action. I understood callbacks, and learned common async patterns. I liked learning a lot about functions, scope, and closures. I used, but never liked callbacks: a nagging doubt said this is the VMs job, not the programmers.

The people who didn't like JavaScript mainly complained about the callbacks, and I understood where they were coming from.

But I was still excited about JavaScript, and for the next few years I'd go to every JS conference and meetup. In 2012-2013 a bunch of talks (and stickers) appeared on Promises. Most conference talks begins with 'explain the problem you are trying to solve'. Every promise talk began the same way: every talk began with a discussion of 'callback hell' and the 'pyramid of doom'.

The problem was, most people I knew using JS weren't using pyramid of doom. We were using async, one of the popular npm packages. Even today it's still the most popular way to handle async flow control - more than q, bluebird or other libraries. If we had a long list of async tasks, that each depended on the result of the previous task, we'd put that list in an array and use async.waterfall(). The tasks would line up neatly on the left. No pyramid.

So when a presenter came along, and talked about how we could replace our existing list of tasks, that neatly lined up on the left, with a new system where, er, things would neatly line up on the left, by refactoring everything, I wasn't particularly convinced. Nor was I alone: the node core folk seemed to agree, ripping an early implementaton of Promises out of node.

If the point of Promises was to fix callback hell, then Promises with .then() wasn't that much better than async.waterfall()

The promise folks began advocating that there was a higher purpose to promises: returning a value. This resonated a little more: functions should return values, whether they're async or not. Even in node a lot of people mistakenly thought return cb(err, result) meant the value you were returning would be used: in reality running the callback and returning null would do the same thing. So sync functions returned values, but async functions didn't return values: they took arguments for functions that operated on their results.

'Functions should return values' was a good point, but still not being able to use the results directly didn't really improve things much.

So .then() never excited me, even after Promises were added to JS in ES2015. But I did keep an eye out - and an open mind - for something I heard was coming: async await.

async/await is what's called direct style. From Wikipedia:

direct style is the usual style of sequential programming, in which control is passed implicitly by simply going to the next line

You still get all the non-blocking behavior JavaScript is known for, with one important difference:

No callbacks, no .then().

Just going to the next line.

Async await is part of the ES2017 standard, in node since version 7 and all current browsers.

await - is a way to await until a promise has returned a value (resolved). Error handling is plain old JavaScript: if something breaks (ie, a promise rejects), an Error is thrown. On the next line, you can just use the value like any other. No more .then() .

- is a way to await until a promise has returned a value (resolved). Error handling is plain old JavaScript: if something breaks (ie, a promise rejects), an Error is thrown. On the next line, you can just use the value like any other. No more . async - a thing you add to the function containing the await keyword. Technically it starts a special context, practically it's a thing you need to add.

Here's a demo:

const util = require ( 'util' ), fs = require ( 'fs' ), writeFile = util.promisify(fs.writeFile), request = require ( 'superagent' ), stat = util.promisify(fs.stat), sorts = require ( 'sorts' ), log = console .log; const getPhotosAndSaveThem = async function ( ) { const response = await request.get( 'https://jsonplaceholder.typicode.com/photos' ); const photos = response.body.sort(sorts.byKey( 'title' )) await writeFile( 'photos.json' , JSON .stringify(photos, null , 2 )) const status = await stat( 'photos.json' ); log( `I got some data from an API and saved the responses at ${status.birthtime} ` ) } const start = async function ( ) { try { await getPhotosAndSaveThem(); } catch (error) { log( `Oh no, something went wrong!` , error); } } start();

CertSimple is built in node. Node 8 came out last week, it will be the first LTS version of node with async/await support. Shortly afterward I decided to spend a day trying async/await on CertSimple's code.

If, after a day, I wasn't remarkably reducing the complexity of code, or if I didn't feel confident with await, I'd stash the changes and not bother.

If it worked out, I'd start using async/await as the default way to handle asyncronous tasks.

Here's what I learned.

This is the best thing to happen to JavaScript in years.

Even for someone who previously avoided Promises if possible, it only took about a day to feel comfortable with async . We've started moving CertSimple's code to async/await .

As we edit, we remove cb and .then() replacing them with await = . We update the unit tests. We forget to add async to functions, then eslint reminds us, then we forget again. Occasionally we commit stuff like this - the code in question looks at DNS, reverse DNS, whois and HTTP headers. To be fair, it was a little pyramid-y:

Six years after I got confused about async, JS now works like I expected it to:

JS now has a syntax that's familiar to traditional Ruby / Python programmers and a speed that isn't.

Of course, await still uses promises. And though I never liked .then() , I'm grateful to the Promise authors for creating them. A huge thanks to Brian Terlson of Microsoft for a bunch of the work in making await itself happen.

This is the best thing to happen to JavaScript in years.