async functions

In “Generators + Promises” in Chapter 4, we mentioned that there’s a proposal for direct syntactic support for the pattern of generators yielding promises to a runner-like utility that will resume it on promise completion. Let’s take a brief look at that proposed feature, called async function.

Recall this generator example from Chapter 4:

  1. run( function *main() {
  2. var ret = yield step1();
  3. try {
  4. ret = yield step2( ret );
  5. }
  6. catch (err) {
  7. ret = yield step2Failed( err );
  8. }
  9. ret = yield Promise.all([
  10. step3a( ret ),
  11. step3b( ret ),
  12. step3c( ret )
  13. ]);
  14. yield step4( ret );
  15. } )
  16. .then(
  17. function fulfilled(){
  18. // `*main()` completed successfully
  19. },
  20. function rejected(reason){
  21. // Oops, something went wrong
  22. }
  23. );

The proposed async function syntax can express this same flow control logic without needing the run(..) utility, because JS will automatically know how to look for promises to wait and resume. Consider:

  1. async function main() {
  2. var ret = await step1();
  3. try {
  4. ret = await step2( ret );
  5. }
  6. catch (err) {
  7. ret = await step2Failed( err );
  8. }
  9. ret = await Promise.all( [
  10. step3a( ret ),
  11. step3b( ret ),
  12. step3c( ret )
  13. ] );
  14. await step4( ret );
  15. }
  16. main()
  17. .then(
  18. function fulfilled(){
  19. // `main()` completed successfully
  20. },
  21. function rejected(reason){
  22. // Oops, something went wrong
  23. }
  24. );

Instead of the function *main() { .. declaration, we declare with the async function main() { .. form. And instead of yielding a promise, we await the promise. The call to run the function main() actually returns a promise that we can directly observe. That’s the equivalent to the promise that we get back from a run(main) call.

Do you see the symmetry? async function is essentially syntactic sugar for the generators + promises + run(..) pattern; under the covers, it operates the same!

If you’re a C# developer and this async/await looks familiar, it’s because this feature is directly inspired by C#’s feature. It’s nice to see language precedence informing convergence!

Babel, Traceur and other transpilers already have early support for the current status of async functions, so you can start using them already. However, in the next section “Caveats”, we’ll see why you perhaps shouldn’t jump on that ship quite yet.

Note: There’s also a proposal for async function*, which would be called an “async generator.” You can both yield and await in the same code, and even combine those operations in the same statement: x = await yield y. The “async generator” proposal seems to be more in flux — namely, its return value is not fully worked out yet. Some feel it should be an observable, which is kind of like the combination of an iterator and a promise. For now, we won’t go further into that topic, but stay tuned as it evolves.

Caveats

One unresolved point of contention with async function is that because it only returns a promise, there’s no way from the outside to cancel an async function instance that’s currently running. This can be a problem if the async operation is resource intensive, and you want to free up the resources as soon as you’re sure the result won’t be needed.

For example:

  1. async function request(url) {
  2. var resp = await (
  3. new Promise( function(resolve,reject){
  4. var xhr = new XMLHttpRequest();
  5. xhr.open( "GET", url );
  6. xhr.onreadystatechange = function(){
  7. if (xhr.readyState == 4) {
  8. if (xhr.status == 200) {
  9. resolve( xhr );
  10. }
  11. else {
  12. reject( xhr.statusText );
  13. }
  14. }
  15. };
  16. xhr.send();
  17. } )
  18. );
  19. return resp.responseText;
  20. }
  21. var pr = request( "http://some.url.1" );
  22. pr.then(
  23. function fulfilled(responseText){
  24. // ajax success
  25. },
  26. function rejected(reason){
  27. // Oops, something went wrong
  28. }
  29. );

This request(..) that I’ve conceived is somewhat like the fetch(..) utility that’s recently been proposed for inclusion into the web platform. So the concern is, what happens if you want to use the pr value to somehow indicate that you want to cancel a long-running Ajax request, for example?

Promises are not cancelable (at the time of writing, anyway). In my opinion, as well as many others, they never should be (see the Async & Performance title of this series). And even if a promise did have a cancel() method on it, does that necessarily mean that calling pr.cancel() should actually propagate a cancelation signal all the way back up the promise chain to the async function?

Several possible resolutions to this debate have surfaced:

  • async functions won’t be cancelable at all (status quo)
  • A “cancel token” can be passed to an async function at call time
  • Return value changes to a cancelable-promise type that’s added
  • Return value changes to something else non-promise (e.g., observable, or control token with promise and cancel capabilities)

At the time of this writing, async functions return regular promises, so it’s less likely that the return value will entirely change. But it’s too early to tell where things will land. Keep an eye on this discussion.