Get started with 33% off your first certification using code: 33OFFNEW

The differences between `async` and `await` in JS

4 min read
Published on 28th June 2024

JavaScript has evolved significantly over the years, especially in the way it handles asynchronous operations. Among the most notable advancements are the async and await keywords, introduced in ECMAScript 2017. These keywords simplify working with promises, making asynchronous code more readable and easier to write. In this article, we'll delve into the differences between async and await, how they work together, and their individual roles in handling asynchronous operations.

For more detailed information, refer to the official documentation:

Understanding Asynchronous JavaScript

Before we dive into async and await, let's briefly review asynchronous programming in JavaScript. JavaScript is single-threaded, meaning it can execute only one task at a time. Asynchronous programming allows JavaScript to perform long-running tasks, like fetching data from a server, without blocking the main thread. This is achieved using callbacks, promises, and, more recently, async/await.

What is async?

The async keyword is used to define an asynchronous function. An async function always returns a promise, and the promise's resolved value is the value returned by the function. If the function throws an error, the promise is rejected with that error.

Defining an async Function

Here's how you define an async function:

async function fetchData() {
    return 'Data fetched';
}

In this example, fetchData is an asynchronous function that returns a promise. The promise resolves with the value 'Data fetched'.

Handling Errors in async Functions

Errors in async functions can be handled using try...catch blocks:

async function fetchData() {
    try {
        let response = await fetch('https://api.example.com/data');
        let data = await response.json();
        return data;
    } catch (error) {
        console.error('Error fetching data:', error);
    }
}

In this example, if an error occurs during the fetch operation, it is caught by the catch block.

What is await?

The await keyword is used inside an async function to pause the execution of the function until a promise is resolved or rejected. It can only be used within an async function.

Using await

Here’s how you use await:

async function fetchData() {
    let response = await fetch('https://api.example.com/data');
    let data = await response.json();
    return data;
}

In this example, the await keyword pauses the execution of fetchData until the fetch promise is resolved. Once resolved, the response is passed to response.json(), which is also awaited until it resolves.

Key Differences Between async and await

While async and await are used together, they serve different purposes. Here are the key differences:

  1. Purpose:

    • async: Defines an asynchronous function and ensures it returns a promise.
    • await: Pauses the execution of an async function until the promise is resolved.
  2. Usage:

    • async: Used to declare a function as asynchronous.
    • await: Used to wait for a promise to resolve or reject within an async function.
  3. Return Value:

    • async: Always returns a promise.
    • await: Returns the resolved value of the promise or throws an error if the promise is rejected.

Practical Examples

Let's explore some practical examples to see how async and await work together.

Fetching Data from an API

Consider an example where we fetch data from an API:

async function getData() {
    try {
        let response = await fetch('https://api.example.com/data');
        let data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('Error fetching data:', error);
    }
}

getData();

In this example, getData is an async function that uses await to wait for the fetch operation and the JSON parsing to complete. The try...catch block handles any errors that might occur during these operations.

Parallel Asynchronous Operations

You can use Promise.all to run multiple asynchronous operations in parallel:

async function getMultipleData() {
    try {
        let [response1, response2] = await Promise.all([
            fetch('https://api.example.com/data1'),
            fetch('https://api.example.com/data2')
        ]);

        let data1 = await response1.json();
        let data2 = await response2.json();

        console.log(data1, data2);
    } catch (error) {
        console.error('Error fetching data:', error);
    }
}

getMultipleData();

In this example, Promise.all is used to wait for both fetch operations to complete before proceeding. This approach is more efficient than awaiting each fetch individually.

Error Handling with async and await

Handling errors in asynchronous code is crucial for robust applications. With async and await, you can use try...catch blocks for error handling, making the code cleaner and more readable compared to traditional promise chaining.

Example of Error Handling

async function fetchDataWithErrorHandling() {
    try {
        let response = await fetch('https://api.example.com/data');
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        let data = await response.json();
        return data;
    } catch (error) {
        console.error('Fetching data failed:', error);
        throw error; // Re-throw the error if needed
    }
}

fetchDataWithErrorHandling()
    .then(data => console.log(data))
    .catch(error => console.error('Error in fetchDataWithErrorHandling:', error));

In this example, if the fetch fails or the response is not OK, an error is thrown and caught in the catch block. The error is also re-thrown to be handled by any further .catch blocks in the promise chain.

Differences in Performance

While async and await simplify the code, it's important to understand their impact on performance. Using await within a loop, for example, can lead to sequential execution, which might not be efficient. Instead, you can use Promise.all for parallel execution.

Sequential Execution Example

async function fetchSequential(urls) {
    for (let url of urls) {
        let response = await fetch(url);
        let data = await response.json();
        console.log(data);
    }
}

Parallel Execution Example

async function fetchParallel(urls) {
    let promises = urls.map(url => fetch(url).then(response => response.json()));
    let results = await Promise.all(promises);
    results.forEach(data => console.log(data));
}

In the parallel execution example, all fetch operations start at the same time, significantly improving performance when dealing with multiple asynchronous tasks.