“There’s a huge amount of excitement out there; people are looking forward to when they can use async functions without transpilation,” said Brian Terlson, from Microsoft’s Edge team, who is the editor of the ECMAScript standard as well as “champion” for the async proposal on the TC39 committee that standardizes ECMAScript. When he tweeted that the async proposal had reached stage four, it got more retweets than anything else he’s tweeted.
Async functions are now stage 4 and will be included in ES2017!
— Brian Terlson (@bterlson) July 29, 2016
“Async programming models allow developers to ask all their questions at once. Developers then react to the answers as those answers are provided. The application is constantly adjusting to the information as it comes in. The user experiences a dynamic application that updates itself instead of being forced to wait an unbounded time for a perfect completed view,” said Naveed Ihsanullah of Mozilla’s platform engineering team, and partly because it’s going to make code more understandable.
“Asynchronous programming is very important for developing the best user experiences. Information is in many places and modern applications seek to seamlessly integrate all those disparate sources into one cohesive view. The instantly loaded and completed web page is all illusion, however. Behind the scenes, numerous requests for information are made on the user’s behalf. Some of these are answered quickly and some may take longer. Some may go unanswered altogether,” he said.
The whole web platform is moving in this direction, pointed out Terlson. Async going into ECMAScript 2017 is “a reflection of the fact that more and more things in the platform are asynchronous, so your code ends up having to deal with more asynchrony. Talking to a web worker is an asynchronous kind of thing, as is any kind of networking. Storage APIs are asynchronous. Service workers are doing a bunch of network stuff, so they’re asynchronous. The new Streams API has a lot of asynchronous pieces in it. As new APIs are added, we’re just discovering more and more sources of asynchrony as the platform grows in capability, so it permeates your code.”
Making Async Bearable
He views async as a ‘vast improvement’ over callbacks because “there’s no pyramid-of-doom nesting callbacks” and Ihsanullah agreed.
The problem is how complex the code structure becomes with a lot of callbacks, and how hard that makes it to work with, Hejlsberg said. “The logic often becomes more complex; what if you have to have conditional branching or you have to have the equivalent of a for loop but with async calls in middle of the loop? You can try to do that mapping yourself, where you have to lift your state into shared object or shared variables and maintain that, but you basically have to write a state machine yourself. State machines are something computers are very good at reasoning about and humans are horrible at reasoning about!”
Async takes care of that, he explained. “It turns out that you can mechanically transform code that’s written in the regular sequential style into asynchronous code using CPS, Continuation Processing Style code rewrites. You can rewrite any program that uses synchronous function calls with returns and turn them into functions that take callbacks — and that’s what powers async. You get to write your code as if it is synchronous and then the compiler rewrites it into asynchronous callback-based code for you and turns your code into a state machine.”
That’s the best way to think about the new async/await feature, he suggests. “The places where you use the await operator to await an asynchronous piece of work, the compiler automatically makes a callback out of the rest of your code.”
“The big benefit is you get to write your code the way you always have. If you need an if statement, you write an if statement, if you need a for loop you write a for loop, and inside those you can say await and then have control return and then come back whenever the async work completes. People are very excited about that, because it makes your code look a lot cleaner and it’s a lot it’s easier to reason about your code.”
As Terlson notes, “For the most part you don’t have to worry about the fact that you’re calling an asynchronous API; you just await it and go about your day. If the promise is rejected you get an exception thrown by async, so you can write asynchronous code that looks like synchronous code and handle errors with normal synchronous imperative code.”
You still have refactoring to do. “When you make a function an async function, code that calls it gets a promise instead of a value, so you do need to change the calling code to async as well, and await the result.” But that’s far easier to do with async because the code itself is less complex. “You can even await non-promises. There are some APIs that return promises but sometimes if they know a value synchronously they just return it, so if you await the API result the right thing will happen.”
Understanding that async and its related function await is based on generators and promises will help you deal with more complex asynchronous code, he said. “Await currently only allows waiting on one thing a time. A developer recognizing that these async functions are promises, however, could then use await Promise.all(…) to wait on several actions.”
Browsers Getting Ready for Async
At one point, async seemed a slightly controversial proposal. “There was some concern around whether blessing promises as the async pattern is the right choice or something more like tasks that could swap out other things under the covers,” Terlson explained. But at that stage there hadn’t been many implementations beyond Microsoft’s Edge browser (starting as an experimental feature in Edge 13.10547 back in September 2015 and moving to an unprefixed version in the Windows Insider preview build 14986).
“Then Google V8 started implementing it and as we got more implementation experience, and people were convinced there were not problems for performance and so on, that helped.”
Now async functions are enabled by default in Chrome, since Chrome 55 (Google’s Jake Archibald called them “quite frankly marvelous”) and they’ve been in the Firefox nightly releases since November 2016 (the plan is to support them in Firefox 52). Opera 42 and later support async, and it’s under development in Safari.
And with transpilers like Babel and TypeScript, you can even write async code and have it run in older browsers, as well as being confident it will work in the latest browsers as they add support.
It’s a lot more work to support async without generators, which is why it used to only be possible to transpile async code to ECMAScript 2015 in TypeScript, and Babel has different techniques depending on which version of ECMAScript you want to target. But now TypeScript 2.1 lets you go all the way back to ECMAScript 3, said Hejlsberg.
“With TypeScript 2.1 we switched to a new emitter; this is the backend of the compiler. It’s a tree writer that rewrites your syntax trees to make these new state machines and the other fancy stuff you have to make, so we now natively support rewriting async await to ECMAScript 3. And it’s not just doing the simple cases where you can only use await at the top level not in the middle of an initialise or for a property of an object literal — no, it’s an operator like any other, so just like you can say plus, you can say wait.”
Browsers and transpilers aren’t the only place async needs to be supported for it to become mainstream of course; frameworks and libraries also need to support it. “If you want to write async style code but you have a bunch of frameworks that weren’t written in that style, those frameworks are still going to do callbacks,” he points out. “Async works fantastically if you have a promise based library that you’re coding against. Often, though, libraries are not promise-based and then you have to promisify them or find a promisified version of that same functionality. That will be the challenge, because that’s the glue that connects you from the callback to the async world.”
Expect that to take time to happen. “As with anything, it’s not happening overnight but there’s going to be an increasingly gradual shift and modern frameworks getting written now will use promises for all their async — and that means it will be a lot easier to consume them with async code. It’s going to be this wave that slowly washes over as opposed to something that happens overnight.”