How and When to Use 'Async' and 'Await'

How and when to use ‘async’ and ‘await’

When using async and await the compiler generates a state machine in the background.

Here's an example on which I hope I can explain some of the high-level details that are going on:

public async Task MyMethodAsync()
{
Task<int> longRunningTask = LongRunningOperationAsync();
// independent work which doesn't need the result of LongRunningOperationAsync can be done here

//and now we call await on the task
int result = await longRunningTask;
//use the result
Console.WriteLine(result);
}

public async Task<int> LongRunningOperationAsync() // assume we return an int from this long running operation
{
await Task.Delay(1000); // 1 second delay
return 1;
}

OK, so what happens here:

  1. Task<int> longRunningTask = LongRunningOperationAsync(); starts executing LongRunningOperation

  2. Independent work is done on let's assume the Main Thread (Thread ID = 1) then await longRunningTask is reached.

    Now, if the longRunningTask hasn't finished and it is still running, MyMethodAsync() will return to its calling method, thus the main thread doesn't get blocked. When the longRunningTask is done then a thread from the ThreadPool (can be any thread) will return to MyMethodAsync() in its previous context and continue execution (in this case printing the result to the console).

A second case would be that the longRunningTask has already finished its execution and the result is available. When reaching the await longRunningTask we already have the result so the code will continue executing on the very same thread. (in this case printing result to console). Of course this is not the case for the above example, where there's a Task.Delay(1000) involved.

How should we use async await?

Every-time you call await it creates a lump of code to bundle up variables, captures the synchronization context (if applicable) and create a continuation into an IAsyncStateMachine.

Essentially, returning a Task without the async keyword will give you a small run-time efficiency and save you a bunch of CIL. Do note that the Async feature in .NET also has many optimizations already. Also note (and importantly) that returning a Task in a using statement will likely throw an Already Disposed Exception.

You can compare the CIL and plumbing differences here

  • Forwarded Task
  • Async Method

So if your method is just forwarding a Task and not wanting anything from it, you could easily just drop the async keyword and return the Task directly.

More-so, there are times when we do more than just forwarding and there is branching involved. This is where, Task.FromResult and Task.CompletedTask come into play to help deal with the logic of what may arise in a method. I.e If you want to give a result (there and then), or return a Task that is completed (respectively).

Lastly, the Async and Await Pattern has subtle differences when dealing with Exceptions. If you are returning a Task, you can use Task.FromException<T> to pop any exception on the the returned Task like an async method would normally do.

Nonsensical example

public Task<int> DoSomethingAsync(int someValue)
{
try
{
if (someValue == 1)
return Task.FromResult(3); // Return a completed task

return MyAsyncMethod(); // Return a task
}
catch (Exception e)
{
return Task.FromException<int>(e); // Place exception on the task
}
}

In short, if you don't quite understand what is going on, just await it; the overhead will be minimal. However, if you understand the subtitles of how to return a task result, a completed task, placing an exception on a task, or just forwarding. You can save your self some CIL and give your code a small performance gain by dropping the async keyword returning a task directly and bypassing the IAsyncStateMachine.


At about this time, I would look up the Stack Overflow user and author Stephen Cleary, and Mr. Parallel Stephen Toub. They have a plethora of blogs and books dedicated solely to the Async and Await Pattern, all the pitfalls, coding etiquette and lots more information you will surely find interesting.

Why use async when I have to use await?

If I use await here, what's the point of the asynchronous method?

await does not block thread. MyMethod2 will run synchronously until it reaches await expression. Then MyMethod2 will be suspended until awaited task (MyMethod) is complete. While MyMethod is not completed control will return to caller of MyMethod2. That's the point of await - caller will continue doing it's job.

Doesn't it make the async useless that VS is telling me to call await?

async is just a flag which means 'somewhere in the method you have one or more await'.

Does that not defeat the purpose of offloading a task to a thread
without waiting for it to finish?

As described above, you don't have to wait for task to finish. Nothing is blocked here.

NOTE: To follow framework naming standards I suggest you to add Async suffix to asynchronous method names.

Async / await vs then which is the best for performance?

await is just an internal version of .then() (doing basically the same thing). The reason to choose one over the other doesn't really have to do with performance, but has to do with desired coding style or coding convenience. Certainly, the interpreter has a few more opportunities to optimize things internally with await, but its unlikely that should be how you decide which to use. If all else was equal, I would choose await for the reason cited above. But, I'd first choose which made the code simpler to write and understand and maintain and test.

Used properly, await can often save you a bunch of lines of code making your code simpler to read, test and maintain. That's why it was invented.

There's no meaningful difference between the two versions of your code. Both achieve the same result when the axios call is successful or has an error.

Where await could make more of a convenience difference is if you had multiple successive asynchronous calls that needed to be serialized. Then, rather than bracketing them each inside a .then() handler to chain them properly, you could just use await and have simpler looking code.

A common mistake with both await and .then() is to forget proper error handling. If your error handling desire in this function is to just return the rejected promise, then both of your versions do that identically. But, if you have multiple async calls in a row and you want to do anything more complex than just returning the first rejection, then the error handling techniques for await and .then()/.catch() are quite different and which seems simpler will depend upon the situation.

Why use async/await, it makes code work like sync

The idea of async/await is as follows:

It is a syntactic sugar for promises. It allows to use async code (like Web Api's - AJAX, DOM api's etc.) in synchronous order.

You ask "But then, why not just use sync code in the first place?" - because in your example you forced a sync code to be returned in a promise, basically you made it async, but it could run as sync in the first place, but there are Web APIs as mentioned above, that work ONLY ASYNC (meaning you have to wait for them to be executed), that's why you cannot turn them in sync "in the first place" :)

When JavaScript encounters the await expression, it pauses the async function execution and waits until the promise is fulfilled (the promise successfully resolved) or rejected (an error has occurred), then it continues the execution where it pauses in that function... The promise can be code from JS engine (as in your case) or it can be from another place like browser apis (truly async).

using async await and .then together

An async function can contain an await expression that pauses the
execution of the async function and waits for the passed Promise's
resolution, and then resumes the async function's execution and
returns the resolved value.

As you can see from below example that you can use two ways to handle await result and errors,The keyword await makes JavaScript wait until that promise settles and returns its result (One you get from resolved promise).So as such there is no harm (I don't fully understand what you refer as harm here).

function returnpromise(val) {  return new Promise((resolve, reject) => {    if (val > 5) {      resolve("resolved"); // fulfilled    } else {      reject("rejected"); // rejected    }  });}
//This is how you handle errors in awaitasync function apicall() { try { console.log(await returnpromise(5)) } catch (error) { console.log(error) }}
async function apicall2() { let data = await returnpromise(2).catch((error) => { console.log(error) })}
apicall2();apicall();

Because I can't run await on the top level, I have to put it into an async function - why can I then call that async function directly?

Top-level await used to not be a thing, but it is possible now in ES6 modules.

One reason why top-level await used to not be a thing, and is still not a thing outside of modules is that it could permit syntactical ambiguity. Async and await are valid variable names. outside of modules. If a non-module script permitted top-level await, then, short of re-working the specification (and breaking backwards compatibility), there would be circumstances when the parser couldn't determine whether a particular instance of await was a variable name, or was used as the syntax to wait for the Promise on its right-hand side to resolve.

To avoid any possibility of ambiguity, the parser, when parsing a section of code, essentially needs to have flags that indicate whether await is valid as an identifier at any given point, or whether it's valid as async syntax, and those two must never intersect.

Module scrips permit top-level await (now) because the use of await as an identifier has always been forbidden in them, so there is no syntactical ambiguity.

In contrast, there are zero issues with using .then on the top level because it doesn't result in any ambiguity in any circumstances.

Why doesn't it just return a Promise which is never executed because it doesn't get awaited?

Promises aren't really "executed". They can be constructed, or waited on to fulfill, or waited on to reject. If you have a Promise, you already have some ongoing code that will (probably) eventually result in a fulfillment value being assigned to the Promise.

Hanging Promises are syntactically permitted - values that resolve to Promises but which aren't interacted with elsewhere. (Which makes sense - every .then or .catch produces a new Promise. If every Promise had to be used by something else, you'd end up with an infinite regress.)

Doing

(async () => {
foo = await someOtherAsyncFunc();
console.log(foo);
})();

is essentially syntax sugar for

someOtherAsyncFunc()
.then((foo) => {
console.log(foo);
});

There's no need to tack anything else onto the end of either of those. (though it's recommended to add a .catch to a dangling Promise so unhandled rejections don't occur)

Learning Promises, Async/Await to control execution order

Yes, that's the correct way to do so. Do realize though that you're blocking each request so they run one at a time, causing inefficiency. As I mentioned, the beauty of JavaScript is its asynchronism, so take advantage of it. You can run all the requests almost concurrently, causing your requests to speed up drastically. Take this example:

// get results...
const getCountry = async country => {
const res = await fetch(`https://restcountries.com/v2/name/${country}`);
const json = res.json();
return json;
};

const getCountryData = async countries => {
const proms = countries.map(getCountry); // create an array of promises
const res = await Promise.all(proms); // wait for all promises to complete

// get the first value from the returned array
return res.map(r => r[0]);
};

// demo:
getCountryData(['portugal', 'ecuador']).then(console.log);
// it orders by the countries you ordered
getCountryData(['ecuador', 'portugal']).then(console.log);
// get lots of countries with speed
getCountryData(['mexico', 'china', 'france', 'germany', 'ecaudor']).then(console.log);


Related Topics



Leave a reply



Submit