async/await
Tags: javascript, webdev
Published: 2023-03-16 06:19:35

> Async/await simplifies asynchronous programming by allowing developers to write code that looks and behaves like synchronous code, improving readability and understandability.

This page is a follow-up branch of the in-depth article "[Async in JS: How does it work](https://stackdiary.com/guides/async-in-js/)", if you'd like to learn about the history of asynchronous JavaScript and its evolution, refer to it.

## In a nutshell

When you use the `async` keyword before a function definition, it indicates that the function will be asynchronous. This means that the function is designed to work with asynchronous operations, such as network requests or reading files, without blocking the main execution thread. In JavaScript, asynchronous functions are usually implemented using promises.

As a result, when you declare a function as `async`, the return value of that function is automatically wrapped in a Promise. Even if the return value is not a promise itself, JavaScript will create a promise that resolves to that value.

Here's an example:

```js
// An asynchronous function
async function getUserName(userId) {
  // Simulate an asynchronous operation (e.g., fetching data from a server)
  const simulatedData = {
    1: "Alice",
    2: "Bob",
    3: "Charlie",
  };

  return simulatedData[userId];
}

// Usage
getUserName(1).then((name) => {
  console.log(`User 1: ${name}`); // Output: User 1: Alice
});
```

In this example, the `getUserName` function is declared as `async`. Even though it returns a simple value from the `simulatedData` object, the return value is automatically wrapped in a promise. When we call the `getUserName` function, we can use the `then` method to handle the resolved value, just like we would with any other promise.

---

Asynchronous functions are essential when dealing with operations that take an unpredictable amount of time to complete, such as making API calls, interacting with databases, or reading files. These operations are not executed immediately; instead, the code sends a request and waits for the response.

The `await` keyword is used inside an `async` function to pause the execution of the function until the asynchronous operation is completed. It allows you to write code that appears synchronous, even though it's performing asynchronous tasks. This makes the code more readable and easier to understand.

```js
// Import the 'fs' module to work with the file system
const fs = require('fs').promises;

// An async function to read a file
async function readFileContent(filePath) {
  try {
    // Use the 'await' keyword to wait for the file to be read
    const content = await fs.readFile(filePath, 'utf-8');
    console.log(`File content: ${content}`);
  } catch (error) {
    console.error(`Error reading the file: ${error.message}`);
  }
}

// Usage
readFileContent('./example.txt');
```

In this example, we're using the `fs.promises` module to read a file. The `readFile` function is asynchronous, so it returns a promise. Inside the `readFileContent` async function, we use the `await` keyword to wait for the `fs.readFile` promise to resolve. Once the promise resolves, the file's content is logged to the console. If there's an error reading the file, the catch block logs the error message.

## Call chains vs async/await

The async/await keywords in JavaScript don't introduce new functionality; they simply make it easier to work with promises. They allow you to write cleaner and more intuitive code compared to using chained `then` and `catch` callbacks.

Here's an example illustrating the difference between using promises with a call chain and using async/await.

**Using a call chain with promises:**

```js
function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Data fetched");
    }, 1000);
  });
}

function processData(data) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`${data} and processed`);
    }, 1000);
  });
}

// Chained promises with then and catch
fetchData()
  .then((data) => {
    return processData(data);
  })
  .then((result) => {
    console.log(result); // Output: Data fetched and processed
  })
  .catch((error) => {
    console.error(`Error: ${error}`);
  });
```

**Using async/await:**

```js
async function fetchDataAndProcess() {
  try {
    const data = await fetchData();
    const result = await processData(data);
    console.log(result); // Output: Data fetched and processed
  } catch (error) {
    console.error(`Error: ${error}`);
  }
}

// Call the async function
fetchDataAndProcess();
```

In both examples, we fetch and process data using promises. The first example uses a call chain with `then` and `catch`, while the second example uses the async/await keywords. The async/await version is easier to understand because it looks more like synchronous code. It's also more straightforward to handle errors using try/catch blocks, making it a popular choice for developers working with asynchronous operations.

## Tips & best practices

Below you'll find some personal tips but also industry best practices when working with async/await.

* Always use async/await instead of then() chains and callbacks.

    * Async/await is easier to read, debug, and manage compared to chaining multiple `then()` and `catch()` functions or using callback functions.

* Await **should not** be used outside of asynchronous functions.
    * The `await` keyword can only be used inside an async function. If you want to perform an asynchronous operation in the global scope, you must use `then()`.
* Use `await Promise.all([...])` to execute several independent asynchronous functions in parallel.
    * Instead of waiting for one asynchronous function to complete before executing the next, you can run multiple independent async functions concurrently using `Promise.all()`. This can help improve the overall performance of your code.

Example:

```js
async function getUser() {
      // Returns the information about the user
    }
    
    async function getNews() {
      // Returns the list of news
    }
    
    // Using Promise.all to run getUser() and getNews() concurrently
    const [user, news] = await Promise.all([getUser(), getNews()]);
```

* Don't mix async/await and Promise.then syntax.
    * To maintain consistency and readability in your code, try to stick to one approach throughout your project: either async/await or Promise.then. Mixing the two can make your code harder to read and maintain.
* Use try/catch blocks for error handling.
    * When using async/await, it's a good practice to handle errors with try/catch blocks. This approach is more consistent and easier to understand compared to handling errors with Promise.catch().

Example:

```js
async function fetchData() {
      try {
        const data = await fetchSomeData();
        console.log("Data fetched:", data);
      } catch (error) {
        console.error("Error fetching data:", error);
      }
    }
```

* Avoid "async/await hell."
    * Similar to "callback hell," "async/await hell" refers to a situation where you have multiple nested async functions, making the code hard to read and maintain. Instead, try to break down your code into smaller, reusable async functions.
* Keep your asynchronous functions focused.
    * Try to create asynchronous functions that perform a single, well-defined task. This approach makes your code easier to understand, test, and maintain.
* Use `Promise.race([...])` when you need the result of the fastest promise.
    * If you have several asynchronous operations and need to get the result of the one that completes first, use `Promise.race()`. It returns a promise that resolves or rejects as soon as one of the input promises settles.

Example:

```js
async function fetchFromAPI_A() {
      // Fetch data from API A
    }
    
    async function fetchFromAPI_B() {
      // Fetch data from API B
    }
    
    // Using Promise.race to get the result from the fastest API
    const fastestResult = await Promise.race([fetchFromAPI_A(), fetchFromAPI_B()]);
```

By adhering to these recommendations, you can improve the readability, maintainability, and performance of your JavaScript code when working with asynchronous operations using async/await and promises.