Tuesday 13 March 2018

Async Await Comparison

I think the async/await pair is one of the most "revolutionary" features added to programming languages in the last years. I have to admit that when they were added to C# it took me a while to wrap my head around them, same as with the yield statement. Both cases come down to the same, a method that gets restarted at an intermediate point, a continuation, it seemed like magic until I understood all the compiler magic involved... Hopefully, the way async/await behaves in JavaScript is pretty similar, so most of the tricks you learnt in C# apply also in JavaScript, but there are some subtle differences. I'll write down here some notes on similarities and differences that I guess I'll be revisiting from time to time.

As you know async/await revolves around Tasks in C# and Promises in JavaScript. If we want to return a Task/Promise from an already existing value rather than from a really asynchronous operation (for testing for example), we can do like this:
C#: Task.FromResult("hi");
JavaScript: Promise.resolve("hi");

The previous feature is particularly useful in C# for testing. In C# you can only await for a Task, so easily creating a Task from a value can be pretty usefult. I thought it would be the same in JavaScript, but to my surprise I've realised that in Javascript you can await for any value, I mean, these 2 lines are equivalent:
let res = await Promise.resolve("hi");
let res = await "hi";

In both languages, marking a method/function as async means that the compiler magic will create a Task/Promise as soon as the method is invoked and that Task/Promise will ultimately contain the final result returned from the function or the exception thrown from it. Even if the exception is thrown before any call to await happens in the function (so it's running synchronous), it won't be available until we await for the function.


async function throwExceptionAsync(st){
 throw {message: "crashed"};
 return st.toUpperCase();
}

console.log("calling throwExceptionAsync");
prs = throwExceptionAsync("hi");
//I get here, so the exception has been wrapped in the promise
console.log("after calling throwExceptionAsync");
console.log(prs.constructor.name); //Promise
//I get the exception in the await 
try{
 res = await prs;
}
catch (ex){
 console.log("exception: " + ex.message);
}


//output:
calling throwExceptionAsync
after calling throwExceptionAsync
Promise
exception: crashed

There's a difference that I found when checking this question in stackoverflow. As I've just said, the compiler automatically creates and returns a Task/Promise for any async method. In JavaScript this Promise will resolve to the value that the function returns, and in C# we'll have a Task<Result> (or Task if the method returns nothing). This means that the code that we write in our async method must return a value, not a Task/Promise of value (as the compiler itself takes care of creating that Task/Promise). This means that in C# we have this:


private async Task<string> GetContentAsync(string url){...}

private async Task<string> FormatAsync(string url){...}

//this is good
public static async Task<string> FormatUrlContent(string url)
{
 string content = await GetContentAsync(url);
 return await FormatAsync(content);  
}

//this is not good, it won't compile:
//error CS4016: Since this is an async method, the return expression must be of type 'string' rather than 'Task<string>
public static async Task<string> FormatUrlContent(string url)
{
 string content = await GetContentAsync(url);
 return FormatAsync(content);  
}

The second method won't compile because our code is returning a Task<string>, that would get wrapped in the Task that the compiler automatically creates, so in the end we would be returning a Task<Task<String>>.

In JavaScript I was not expecting an error, just that we would get a Promise that would resolve to another Promise that would resolve to a string, but to my surprise the compiler seems to take this into account and returns a Promise that will be resolved when the internal Promis is resolved (so indeed it's doing the same as it does with a call to then that also returns a promise.

async function anotherAsyncMethod(){
 return Promise.resolve("hi");
}

prs = anotherAsyncMethod();
console.log(prs.constructor.name); //Promise
res = await prs; //this is already the string not a Promise of string
//console.log(res.constructor.name); //String
console.log(res);

There's another difference that I'd like to mention, though it's not about async/await, but about Tasks/Promises. In C#, if you want you can block your code (which in general is quite a bad idea) waiting for the result of the async call, by doing either Task.Wait() or Task.Result. In JavaScript, promises lack any blocking method, you can only access the result in a callback function provided to then() (or obviously through await magic.

No comments:

Post a Comment