async/await is a mechanism in JavaScript that makes working with asynchronous IO easier. But it seems to be hard to understand. In this article, I will introduce my intuitive understanding of async/await.

## Idea

In our daily work, our tasks may contain not only the action part but also the waiting part.
Sometimes, we need to wait for an event such as the response from someone else or our other task to be finished before the task can continue.

To improve efficiency, we may have multiple tasks in progress at the same time. If the current task reaches the waiting part, we may switch to another task, and go back later. In this way, we make use of the waiting time of one task by doing other tasks. We can also save time by waiting for multiple events at the same time.

The concept of asynchronous and concurrent are based on this idea. If some tasks are in progress at the same time, we say these tasks are executed concurrently. We call the task that can be executed concurrently with others an asynchronous task. In JavaScript, most of the IO operations, the operations whose major parts are “waiting”, are asynchronous. The tranditional IO function starts a new asynchronous task that does the IO operation then executes the call-back function. The caller continue to execute once the IO operation reaches the waiting part. The caller may start another asynchronous IO operation which is executed concurrently with other unfinished IO operations.

However, in traditional JavaScript, it is not convienient to perform an asynchronous IO operation and wait for it in the middle of a task. We have to split the task into many call-back functions, which can make the code hard to write and hard to understand. The async/await feature is to solve this issue. We introduce promise object to represent an asynchronous task. We can mark the function that starts an asynchronous task and returns a promise with async. Inside the function, we implement the task, and we can use await on a promise object to wait for a the corresponding task and get its output. The a in await indicates that the waiting is for asynchronous task, which means, we will try to switch to another in-progress asynchronous task while waiting. [1]

## Promise

As mentioned earlier, we use a promise object to represent an asynchronous task that has started. At a certain time, a promise can be

• resolved with a value, i.e. a successful result,
• rejected with a reason, i.e. failed for a reason,
• not finished yet.

In TypeScript, the promise has a Promise<T> type where T is the resolve value type.

Beside accepting a call-back function, an asynchronous IO function can return a promise object to represent the IO task. There are some libraries providing such API, e.g. fs/promises. You can also use new Promise(...)[2] to turn a call-back style API to a promise.

## async/await

Now we want to build a more complex asynchronous task that includes waiting for some other tasks. It’s where async/await becomes useful. You can mark the function or lambda with async keyword to indicate that it starts a new task. The async keyword has the following effects.

• The function always returns a promise, representing the started task. In TypeScript, the return type has to be Promise<T> where T can be any type.
• The function body becomes what the task does. In the function body,
• the value returned become the promise resolve value.
• the error thrown become the promise reject reason.
• you can use await on a promise object, which waits for the task, either giving you the resolved value or throwing the reject reason.

Here is an example of async and await.

If you want to use await at top-level, you can create an async lambda and immediately execute it, like this

To elaborate that the tasks are indeed executed concurrently, let’s create an example that starts 1000 tasks of performing an IO operation twice. Here we use setTimeout to simulate a long-waiting IO.

Here we execute task function but do not await on it. This makes it starts the 1000 tasks immediately. Running this code, you should notice that after roughly 2 seconds, 1000 Task #n has finished its first longIO printed, then after roughly 2 seconds, 1000 Task #n has finished its second longIO printed. This shows that the tasks are executed concurrently. They wait for the longIO at the same time and they are in progress at the same time.

## Summarize

This article briefly introduces the idea of async/await and discusses how to achieve concurrency. Because I’m too lazy I don’t want this article to be too long, I didn’t go into all the details. I hope this article dispels your fear of async/await.

1. DISCLAIMER: this explaination of the name await is unofficial. ↩︎