1. javascript
  2. /async
  3. /async-await

Async/аwait Functions in JavaScript

Introdcution

Asynchronous programming can make your code run more efficiently and avoid blocking the main thread, but it can also make it more complex and harder to reason about.

Async/await functions are a way to simplify asynchronous code in JavaScript and serve as syntactical sugar wrapped around promises, making it easier to read and write complex asynchronous logic.

Prerequisites

Before diving into async and await, you need to have a solid understanding of promises.

As we mentioned in our previous article, promises are a way to handle asynchronous results in a more synchronous-like fashion. They have a then method that can be used to register a callback function that will be called when the promise is resolved and a catch method that can be used to register a callback function that will be called when the promise is rejected.

We can refresh our knowledge with the following example of using both methods to handle the result of an asynchronous function that returns a promise:

myAsyncFunction()
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(error);
  });

The Syntax

The async keyword is used to define an asynchronous function that returns a promise, allowing us to use the await keyword inside the function. The await keyword "waits" for a promise to be resolved before moving on to the next line of code. Here's an example of an asynchronous function that returns a resolved promise after a delay using setTimeout:

async function delayedLog() {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  console.log("1 second has passed");
}
delayedLog();

the delayedLog() function is asynchronous and returns a resolved promise after one second. Then, the await keyword is used to wait for the promise to be resolved before logging a message.

Another example would be fetching data from an API:

async function getData() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts");
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.log(error);
  }
}
getData();

We're using the fetch() function to make a GET request to the specified API endpoint. The await keyword is used to wait for the response to be returned before parsing the data as JSON.

Similarly, we can use async and await when handling user input asynchronously:

async function handleSubmit(event) {
  event.preventDefault();
  const input = document.querySelector("input");
  const value = await new Promise((resolve) => {
    setTimeout(() => {
      resolve(input.value);
    }, 1000);
  });
  console.log(`You typed: ${value}`);
}
document.querySelector("form").addEventListener("submit", handleSubmit);

In this example, handleSubmit handles the form submission event, and the await keyword waits for the user input to be resolved from the promise, which is resolved after 1 second.

Also, let's take a look at a more elaborate example of using async/await in an e-commerce web application to handle user registration and login:

// Common function for making POST requests
const makeRequest = async (url, body) => {
  try {
    const response = await fetch(url, {
      method: 'POST',
      body: JSON.stringify(body),
      headers: { 'Content-Type': 'application/json' }
    });
    const data = await response.json();
    if (data.error) {
      throw new Error(data.error);
    }
    return data;
  } catch (error) {
    console.error(error);
  }
}

// Function for user registration
const registerUser = async (email, password) => {
  // Make the POST request to register the user
  const data = await makeRequest('/api/register', { email, password });
  
  // Log success message
  console.log('User registered successfully');
  
  // Return the token
  return data.token;
}

// Function for user login
const login = async (email, password) => {
  // Make the POST request to login the user
  const data = await makeRequest('/api/login', { email, password });
  
  // Log welcome message
  console.log(`Welcome ${data.name}!`);
  
  // Return the token
  return data.token;
}

In this example, we have two async functions, registerUser and login that are handling the registration and login of the user by making a POST request to the server using the fetch function. The await waits for the response to be returned before parsing the data as JSON. If there's an error, it will be caught by the catch block and logged in the console.

Keep in mind that this is just a simplified example, and in real-world implementation, we should also handle the token received and store it securely in the browser or a backend service, and also validate the user's input before making the requests.

Conclusion

While async/await functions provide a convenient way to handle asynchronous code, they are not suitable for every situation. Aside from the summary, we'll also include some of the limitations and nuances:

  • Async/await is a powerful tool for handling asynchronous code, making it more readable and maintainable.

  • The technique is built on top of Promises, so understanding promises is a must.

  • Always use try-catch to handle errors and avoid chaining multiple await statements.

  • Async/await is suitable for handling promises, setTimeout, or fetch API, but not for synchronous code.

  • Async/await can be slower than Promises, especially when used in tight loops or when the promise is already resolved, so it's important to optimize its usage.

  • The technique isn't a silver bullet and its usage must be optimized and weighed based on the specific use case.

  • Mastering it takes time and practice, but it can be a game-changer when it comes to making your code more readable and maintainable.