The question hinges on whether the functions called in your example (doExpensiveOperation
etc) are synchronous or asynchronous operations. Most JavaScript functions are synchronous. They do some stuff and return when they are finished. An asynchronous operation starts some background task (for example a network request), and then immediately returns - but the task runs to completion in the background and executes a callback function when finished.
Note that because JavaScript is single-threaded it is not possible to have JavaScript code execute in the background1. Asynchronous operations are built-in API functions like network requests or timers which can run outside of the JavaScript engine.
If the functions in your example are synchronous, then adding await
has no effect whatsoever. The function is called, and when it returns, execution continues.
But if the functions are asynchronous we have an interesting difference. Lets use the example of the fetch
API where a HTTP request is executed asynchronously. Example in pseudocode:
function 1
const promise = fetch('examplecom/api');
// rest of code
function 2
const result = await fetch('examplecom/api');
// rest of code
Now the difference is significant. In the first example, the code continues immediately after the request has been initiated. In the second example, the code waits until the response has been fully received. Which one to use depends on what behavior you want. If you are storing some data on the server, you might want to continue immediately since you don't need the response ("fire and forget"). If you are fetching some data, you might want to wait until it is received before you do something with the data.
The version with await
is equivalent to passing a callback to be executed when the request is finished, like:
fetch('examplecom/api')
.then((result) => {
// rest of code;
});
Callbacks like this quickly become tedious to write though, and await
is introduced as syntactic sugar to allow you to write async code (almost) as straightforward as regular synchronous code.
Now imagine if there existed a synchronous counterpart to fetch
called fetch_synchronous
, which blocked until the request was complete. Code would look like this:
synchronous version
const result = fetch_synchronously('examplecom/api);
// rest of code
This looks very close to the version using await
, and it also only continues when the result has been fetched. But there is a significant difference: Because JavaScript is single-threaded, when execution is blocked waiting for a synchronous function to complete, no other code can be executed - not even event handlers reacting to the user input. This means the whole page freezes and becomes unresponsive until the request is complete. This is obviously a bad user experience, and synchronous requests are therefore deprecated.
It is important to emphasize that await
cannot turn a synchronous function in an asynchronous function or a blocking operation into a non-blocking operation. Therefore the use of await
does not in itself make the application more responsive or faster or more concurrent. But if you have the choice between a synchronous and an asynchronous operation, using an asynchronous will increase responsiveness for the user.
(1) It is possible to have multithreaded JavaScript using WebWorkers, but this is a whole different can of worms, as they cannot be invoked with normal function calls.
await
is quite different despite the similar-looking syntax. In a multi-threaded language, a blocking operation does not make the application unresponsive or inefficient, since other threads will continue running and use the available cycles. Non-blocking operations are preferred to avoid thread starvation, where all available threads are blocked by IO operations, so the OS needs to create new threads.