# `async`-`await`

## Recap of `Promise`s

JavaScript's `Promise` abstraction was discussed in the [notes](../promises/promises.md) near the start of the course and you had a chance to use it on the Make project.
I'll give a quick refresher since `Promise`s are essential to the `async`-`await` syntax.

A `Promise` represents an asynchronous task or computation.
When the task or computation finishes, the `Promise` "resolves" to a value.
If an error occurs, the `Promise` "rejects" with the error.

The usefulness of `Promises` comes from the ways they can be composed into more complex `Promise`s.
There are two main ways to combine `Promise`s:
- In sequence.
  `.then()` chains two `Promise`s into a `Promise` that performs the asynchronous tasks or computations one after another.
  You can build long chains by calling `.then()` more times:
  ```js
  // Build a Promise that performs 4 asynchronous computations in sequence
  promiseA
    .then(a => { /* build and return promiseB... */ })
    .then(b => { /* build and return promiseC... */ })
    .then(c => { /* build and return promiseD... */ })
  ```
- In parallel.
  `Promise.all()` combines an array of `Promise`s into a `Promise` that performs all the tasks or computations in parallel and resolves to an array containing all their results:
  ```js
  // Build a promise that performs 3 asynchronous computations in parallel
  // and resolves with their results once they have all finished
  Promise.all([promiseA, promiseB, promiseC])
  ```

## `async`-`await` syntax

`Promise`s can greatly simplify asynchronous programs.
Frequently, several asynchronous tasks need to happen in sequence.
In this case, `.then()` provides an interface that looks closer to how the program would be written in a synchronous language.
Recently, new syntax was added to JavaScript that makes asynchronous code look even more like synchronous code.
It relies on two keywords:
- `async`: we can declare an "async function" by putting `async` in front of it:
  ```js
  async function name(...arguments) {
    /* ... */
  }
  // or
  async (...arguments) => { /* ... */ }
  ```
  Unlike normal functions, `async` functions return `Promise`s.
  If the function `return`s a value, the `Promise` resolves with that value.
  If the function `throw`s an error, the `Promise` rejects with that error.
- `await`: inside an `async` function, the keyword `await` can be used to "wait" for a `Promise` to resolve.
  If the `Promise` throws an error, it can be caught by a `try`-`catch` block.

It is easiest to explain `async`-`await` with some examples.

## Example: recursively exploring a directory

We can rewrite the code from the `Promise`s notes for [computing the modification time of a directory](../promises/promises.md#promise.all).
Recall that we want to find the latest modification time of any file inside the directory (or any subdirectories).
By making `modifiedTime()` an `async` function, we can use `await` instead of explicit `.then()` calls on `Promise`s.
You can compare this [`async`-`await` version](await-mtime.js) with the [`Promise`s version](../promises/recursive-mtime.js).
```js
// Returns a Promise<number> that computes the most recent
// modification time for any file inside a directory
async function modifiedTime(path) {
  // First, we wait for the fs.stat() Promise
  const stats = await fs.stat(path)
  // Use the modification time of the file or directory itself
  let latestTime = stats.mtimeMs
  // If this is a directory, check all files inside for later modified times
  if (stats.isDirectory()) {
    // Wait to get a list of all files/subdirectories in the directory
    const files = await fs.readdir(path)
    // Then get the modified time of each file/subdirectory
    for (const file of files) {
      const fileModifiedTime = await modifiedTime(path + '/' + file)
      // update latestTime if the modification time is later
      latestTime = Math.max(latestTime, fileModifiedTime)
    }
  }
  // Return the latest modification time
  return latestTime
}
```

However, this code no longer explores subdirectories in parallel, which was the main advantage of asynchronicity.
Can you see why?

The issue is that we have replaced `Promise.all(files.map(file => /* ... */))` with a `for (const file of files)` loop.
Since `await modifiedTime(path + '/' + file)` *waits* for `modifiedTime()` to finish, the loop waits for `file` to be completely explored before continuing.
This means the subdirectories are explored sequentially.

If we want to perform asynchronous tasks in parallel inside an `async` function, **we still need to use `Promise.all()`**.
[Here](await-mtime-parallel.js) is a parallel version using `Promise.all()`:
```js
async function modifiedTime(path) {
  const stats = await fs.stat(path)
  const {mtimeMs} = stats
  // If this path is a file, return its modification time
  if (!stats.isDirectory()) return mtimeMs

  // If this is a directory, check all files inside for later modified times
  const files = await fs.readdir(path)
  // Get the modified time of each file/subdirectory in parallel
  const modifiedTimes = await Promise.all(files.map(file =>
    modifiedTime(path + '/' + file)
  ))
  // Return the latest modification time
  return Math.max(mtimeMs, ...modifiedTimes)
}
```

## Example: emoji search

The [Unicode Consortium](https://home.unicode.org) defines the mapping from numbers to characters (including emoji) that almost everyone uses.
For example, Unicode defines 65 to mean `A`, 233 to mean `é`, and 128027 to mean `🐛`.

We use this mapping to make an [emoji search](await-emoji.js) that finds a Unicode character by name (e.g. `pile of poo`) and prints out the character.
We first fetch the current Unicode version (updated each year) from Unicode's [`ReadMe.txt`](https://unicode.org/Public/zipped/latest/ReadMe.txt) file.
We then fetch the current [`UnicodeData.txt`](https://www.unicode.org/Public/13.0.0/ucd/UnicodeData.txt) file which lists the tens of thousands of Unicode characters.
Note how `await` allows us to execute 4 `Promise`s sequentially in `getUnicodeNumber()` without needing to combine them with `.then()`.
```js
async function getUnicodeNumber(searchName) {
  // Fetch the readme to determine the latest Unicode version (e.g. 13.0.0)
  const readmeResponse = await fetch(README_URL)
  const readme = await readmeResponse.text() // read readme as a string
  // The last line of the readme contains the current Unicode URL,
  // e.g. https://www.unicode.org/Public/13.0.0/ucd/
  const [latestURL] = readme.trim().split('\n').slice(-1)

  // Then fetch the list of Unicode characters
  const charactersResponse = await fetch(latestURL + UNICODE_FILE)
  const charactersData = await charactersResponse.text()
  // Each line of the data file corresponds to one Unicode character
  for (const characterData of charactersData.split('\n')) {
    // Each line has several fields separated by `;`.
    // The first is the character's number and the second is its name.
    const [characterNumber, characterName] = characterData.split(';')
    if (characterName.toLowerCase() === searchName) {
      // If this is the requested character, read its number in base-16
      return parseInt(characterNumber, 16)
    }
  }
  return undefined
}

async function printUnicode(searchName) {
  // getUnicodeNumber() returns a Promise, so we need to await it
  const number = await getUnicodeNumber(searchName)
  // Convert the Unicode number to a character
  if (number !== undefined) console.log(String.fromCodePoint(number))
}
```
(`fetch()` turns an HTTP/HTTPS request into a `Promise`, as explained in the [HTTP notes](../http/http.md#aside-fetch).)

## Example: create files without overwriting existing files

When an `await`ed `Promise` rejects with an error, you can catch it using a `try`-`catch` block, just like a normal JavaScript error.

For [example](await-no-overwrite.js), suppose we want to create some files but not overwrite any existing files with the same name.
We can use the `wx` [flag](https://nodejs.org/api/fs.html#fs_file_system_flags) to make [`fs.writeFile()`](https://nodejs.org/api/fs.html#fs_fspromises_writefile_file_data_options) reject if a file already exists.
We want either all or none of the files to be created, so if one already exists, we stop and remove all the files that were already created.
```js
// Creates an array of files. If any file already exists,
// no files are created and an error is thrown.
async function writeFiles(files) {
  for (let i = 0; i < files.length; i++) {
    const file = files[i]
    try {
      // Write to the file, but throw an error if the file exists
      await fs.writeFile(file.name, file.contents, {flag: 'wx'})
    }
    catch (e) {
      // File already exists, so remove all the previously created files
      await Promise.all(files.slice(0, i).map(file =>
        fs.unlink(file.name)
      ))
      // Re-throw the error
      throw e
    }
  }
}

// Try to create 3 files
writeFiles([
  {name: 'a.txt', contents: 'abc'},
  {name: 'b.txt', contents: '123'},
  {name: 'c.txt', contents: 'xyz'}
])
  .catch(_ => console.log('Some files already existed'))
```
You may notice that the files are not written in parallel.
It is possible to fix this by `Promise.all()`ing the `fs.writeFile()`s, but this is tricky since the `Promise` returned by `Promise.all()` rejects as soon as *any* of the `Promise`s reject.
If the the `Promise.all()` rejected, we would need to wait for each file to finish being written before trying to remove it.