38. Async functions
Roughly, async functions provide better syntax for code that uses Promises.
38.1. Async functions: the basics
Consider the following async function:
The previous rather synchronous-looking code is equivalent to the following Promise-based code:
A few observations about the async function fetchJsonAsync()
:
Async functions are marked with the keyword
async
.Inside the body of an async function, you write Promise-based code as if it were synchronous. You only need to apply the
await
operator whenever a value is a Promise. That operator pauses the async function and resumes it once the Promise is settled:- If the Promise is fulfilled,
await
returns the fulfillment value. - If the Promise is rejected,
await
throws the rejection value.
- If the Promise is fulfilled,
The result of an async function is always a Promise:
- Any value that is returned (explicitly or implicitly) is used to fulfill the Promise.
- Any exception that is thrown is used to reject the Promise.
BothfetchJsonAsync()
andfetchJsonViaPromises()
are called in exactly the same way, like this:
38.1.1. Async constructs
JavaScript has the following async versions of synchronous callable entities. Their roles are always either real function or method.
38.1.2. Async functions always return Promises
Each async function always returns a Promise.
Inside the async function, you fulfill the result Promise via return
(line A):
As usual, if you don’t explicitly return anything, undefined
is returned for you:
You reject the result Promise via throw
(line A):
38.1.3. Returned Promises are not wrapped
If you return a Promise p
from an async function, then p
becomes the result of the function (or rather, the result “locks in” on p
and behaves exactly like it). That is, the Promise is not wrapped in yet another Promise.
Recall that any Promise q
is treated similarly in the following situations:
resolve(q)
insidenew Promise((resolve, reject) => { ··· })
return q
inside.then(result => { ··· })
return q
inside.catch(err => { ··· })
38.1.4. await: working with Promises
The await
operator can only be used inside async functions. Its operand is usually a Promise and leads to the following steps being performed:
- The current async function is paused (similar to how sync generators are paused when you
yield
). - Processing of the task queue continues.
- Once the Promise is settled, the async function is resumed:
- If the Promise is fulfilled,
await
returns the fulfillment value. - If the Promise is rejected,
await
throws the rejection value.
The following two sections provide more details.
- If the Promise is fulfilled,
38.1.5. await and fulfilled Promises
If its operand ends up being a fulfilled Promise, await
returns its fulfillment value:
Non-Promise values are allowed, too, and simply passed on (synchronously, without pausing the async function):
38.1.6. await and rejected Promises
If its operand is a rejected Promise, then await
throws the rejection value:
Instances of Error
(which includes instances of its subclasses) are treated specially and also thrown:
38.2. Terminology
Let’s clarify a few terms:
Async functions, async methods: are defined using the keyword
async
. Async functions are also called async/await, based on the two keywords that are their syntactic foundation.Directly using Promises: means that code is handling Promises without
await
.Promise-based: a function or method that delivers its results and errors via Promises. That is, both async functions and functions that return Promises, qualify.
Asynchronous: a function or method that delivers its results and errors asynchronously. Here, any operation that uses an asynchronous pattern (callbacks, events, Promises, etc.) qualifies. Alas, things are a bit confusing, because the “async” in “async function” is an abbreviation for “asynchronous”.
38.3. await is shallow (you can’t use it in callbacks)
If you are inside an async function and want to pause it via await
, you must do so within that function, you can’t use it inside a nested function, such as a callback. That is, pausing is shallow.
For example, the following code can’t be executed:
The reason is that normal arrow functions don’t allow await
inside their bodies.
OK, let’s try an async arrow function, then:
Alas, this doesn’t work, either: Now .map()
(and therefore downloadContent()
) returns an Array with Promises, not an Array with (unwrapped) values.
One possible solution is to use Promise.all()
to unwrap all Promises:
Can this code be improved? Yes it can, because in line A, we are unwrapping a Promise via await
, only to re-wrap it immediately via return
. We can omit await
and then don’t even need an async arrow function:
For the same reason, we can also omit await
in line B.
38.4. (Advanced)
All remaining sections are advanced.
38.5. Immediately invoked async arrow functions
If you need an await
outside an async function (e.g., at the top level of a module), then you can immediately invoke an async arrow function:
The result of an immediately invoked async arrow function is a Promise:
38.6. Concurrency and await
38.6.1. await: running asynchronous functions sequentially
If you prefix the invocations of multiple asynchronous functions with await
, then those functions are executed sequentially:
That is, otherAsyncFunc2()
is only started after otherAsyncFunc1()
is completely finished.
38.6.2. await: running asynchronous functions concurrently
If we want to run multiple functions concurrently, we need to resort to the tool method Promise.all()
:
Here, both asynchronous functions are started at the same time. Once both are settled, await
gives us either an Array of fulfillment values or – if at least one Promise is rejected – an exception.
Recall from the previous chapter that what counts is when you start a Promise-based computation – not how you process its result. Therefore, the following code is as “concurrent” as the previous one:
38.7. Tips for using async functions
38.7.1. Async functions are started synchronously, settled asynchronously
Async functions are executed as follows:
- The Promise
p
for the result is created when the async function is started. - Then the body is executed. There are two ways in which execution can leave the body:
- Execution can leave permanently, while settling
p
:- A
return
fulfillsp
. - A
throw
rejectsp
.
- A
- Execution can also leave temporarily, when awaiting the settlement of another Promise
q
viaawait
. The async function is paused and execution leaves it. It is resumed onceq
is settled.
- Execution can leave permanently, while settling
- Promise
p
is returned after execution has left the body for the first time (permanently or temporarily).
Note that the notification of the settlement of the resultp
happens asynchronously, as is always the case with Promises.
The following code demonstrates that an async function is started synchronously (line A), then the current task finishes (line C), then the result Promise is settled – asynchronously (line B).
38.7.2. You don’t need await if you “fire and forget”
await
is not required when working with a Promise-based function, you only need it if you want to pause and wait until the returned Promise is settled. If all you want to do, is start an asynchronous operation, then you don’t need it:
In this code, we don’t await .write()
, because we don’t care when it is finished. We do, however, want to wait until .close()
is done.
38.7.3. It can make sense to await and ignore the result
It can occasionally make sense to use await
, even if you ignore its result. For example:
Here, we are using await
to join a long-running asynchronous operation. That ensures that the logging really happens after that operation is done.