5 Feb 2017

Compilers Should Automatically Handle async and await

Asynchronous code is hard to write because of nesting: each asynchronous API you invoke adds another level of nesting. Propagating errors from each callback to the next is hard. And so on.

Async / await helps, by eliminating the nesting, and letting you write straight-line code, which is converted behind the scenes into nested code.

What's Async/Await?

Imagine you had this code:

String greeting() {
    Person p = person();
    return "Hello " + p.name();
}

where person() is a synchronous function.

Now imagine that person() became asynchronous, because it needs to fetch the person object from a server. So you need to make greeting() itself asynchronous. Obviously — a synchronous API can't invoke an async one this way.

In most languages, you'd have to register a callback on person() and create another callback based on that. But writing asynchronous code is hard and bug-prone.

If you're programming in a language that has async/await, you would update the code to something like:

async Task<String> greeting() {
    return "Hello " + await name();
}

There are three changes:

  • Use the await keyword to invoke an asynchronous function as if it's synchronous.
  • Decorate the calling function with the async keyword.
  • The function no longer returns a String, but Task<String>, which is an async object that, when it finishes its network call, gives you a String.
The key point is that you've built an async function on top of another by coding in a synchronous style, as opposed to having to register callbacks.

What Could Be Better?

The compiler should figure out where to insert the await call, which is when you have a Task<T> that you're trying to treat as a T. It should be able to figure this out in a statically typed language like C#, Java or Swift.

Similarly, you shouldn't have to add the async keyword to a function. Any function that uses await, explicitly or implicitly, is async, so you shouldn't have to tell the compiler that. It's redundant.

Of the three changes you had to make when converting a synchronous function to asynchronous, the only one that you still need to do is change the return type:

Task<String> greeting() {
    Person p = person();
    return "Hello " + p.name();
}

The IDE could have a Refactor > Make Async menu item that changes the return type of the method, and all its callers, and all their callers, and so on up the call stack, for you.

That makes async/await seamless to use, almost as simple as writing synchronous code.

No comments:

Post a Comment