Analysis / Technology /

JavaScript Will Finally Get Proper Asynchronous Programming

1 Feb 2017 1:00am, by

The proposal to include async function in ECMAScript has reached stage four; that means it’s on track to be in the 2017 release of the standard. But what does that mean for JavaScript developers?

There’s a lot of interest in async, the capabilities JavaScript will need to easily execute multiple functions in parallel.

“Because JavaScript is single-threaded, that means if you have any long-running work it has to happen asynchronously for your app to remain responsive or it would just block and your browser would freeze,” said Anders Hejlsberg, the lead architect of C# and now also a core developer for Microsoft’s TypeScript transpiler for JavaScript. So the JavaScript runtime libraries and all the frameworks are designed such that they only have asynchronous ways of doing things. If you want to do an expensive operation like an XML HTTP request, you don’t get to block and await for the result; you get to supply a callback that calls you back later with the result.”

“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 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.”

The growth of APIs that add asynchrony means JavaScript needs better ways of handling that in code than callbacks. “If you have just one source of asynchrony a callback is OK but if you’re got a lot of them it sucks, and it’s also painful for performance reason with lots of functions created and thrown away.” Essentially, notes Terlson, reiterating over the HTML Document Object Model (DOM), again and again, isn’t efficient.

Making Async Bearable

He views async as a ‘vast improvement’ over callbacks because “there’s no pyramid-of-doom nesting callbacks” and Ihsanullah agreed.

“While async programming has many benefits, writing applications in this style is often complex and tedious. The JavaScript language has had low-level async facilities, such as XMLHttpRequest, for years. These lower level callback-based constructs were very difficult to work with, difficult to maintain and difficult to debug. They could degenerate the source code to Callback Hell as multiple nested requests were made. Asynct has the potential for greatly decreasing the barrier to writing high quality maintainable asynchronous code. These developer benefits translate directly to more responsive applications for users as more async is used.”

In many ways, this is JavaScript catching up with other languages, like C# which pioneered asynchronous programming, and the way asynchrony will work in JavaScript is very similar to how it’s handled in C#, Hejlsberg said. That makes code easier to read and to think about.

“JavaScript is a single-threaded execution environment. If you want anything to happen as a result of asynchrony, you’ve got to do through callbacks or someone has to call you, because only there’s one thread of execution. If someone else has it, they’ve got to give it up and let you run. So from day one, JavaScript always had callbacks like setTimeout or like DOM events; all that happens by someone calling you back.

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.”

Being able to write code using these familiar, synchronous patterns while getting all the benefits of asynchrony “greatly simplifies writing code for dynamic and responsive web applications” said Ihsanullah. He suggests thinking of it as a “syntactic wrapper over JavaScript promises and generators” and points out that “understanding these features will greatly facilitate a developer’s understanding of async” and “experience with promises is probably mandatory.”

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.”

Plus, explained Ihsanullah, JavaScript needed those building blocks of async: promises and generators. “While promises, by virtue of being implementable in JavaScript, have been usable in browsers for a few years, generators are a more recent addition to the language.” And getting the syntax to reflect the way developers use functions is important. “It started with arrows. Then generators. And now async functions. To the standards committee and to the community, ergonomics of the language matters.”

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.

Transpilers

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.

“Once you rewrite your code into a state machine, if you have generators then the transformation is relatively simple — rewriting an async function with awaits in it into a generator is almost trivial. But if you don’t have generators it is much more complex — because now you have to wrap a state machine around your code and effectively every place you see await, the function has to return and then when control comes back it has to jump back there and continue executing. And since there are no gotos in JavaScript, that’s complicated. So you have to write a while loop with a switch statement with a bunch of machine-invented states that you then maintain. The rewrite that happens to your code is complex and getting that rewrite correct is not simple.

“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.”

Announcing that async will be in ECMAScript 2017 will help with this. “Library authors will have to transpile async for some time,” Terlson predicts, “but they can now be confident it’s a future direction for JavaScript so they can use it and not be concerned it’s going to be broken by future language changes.”

And of course, writing asynchronous JavaScript will be new to many developers. “If you’re not using a transpiler today, you haven’t used async functions,” Terlson points out. Anyone already using async is an early adopter, but as ECMAScript 2017 moves towards ratification, now is the time to start looking at how it can improve your code.

Feature image via Pixabay.


A digest of the week’s most important stories & analyses.

View / Add Comments

Please stay on topic and be respectful of others. Review our Terms of Use.