Language: EN

javascript-async-y-await

Async and await in JavaScript

In JavaScript, async and await are a syntax for writing asynchronous code that is easier to read and maintain than previous alternatives (such as callbacks and promises).

Introduced in ECMAScript 2017, async/await allows us to write code sequentially, instead of nesting callbacks or chaining promises.

Internally, async/await is based on promises. In fact, then/catch and async/await are functionally equivalent.

But async/await provides a more convenient syntax to use, which is syntactic sugar for simplifying our lives.

Another advantage of async/await is that it handles errors in a much simpler way. With async/await, you can use the try/catch structure (which is more familiar and easier to understand for developers).

Async/Await Syntax

The async/await syntax is based on two keywords:

  • async is placed before the function to indicate that it contains asynchronous code.
  • await is placed before any operation that returns a promise to indicate that the code must wait for the promise to resolve before continuing.

Async

The async keyword is used to declare an asynchronous function. A function marked with async always returns a promise.

  • If the function returns a value, the promise is resolved with that value.
  • If the function throws an exception, the promise is rejected with that exception.
async function myFunction() {
  return 'Hello, world';
}

myFunction().then(console.log); // 'Hello, world'

In the example above, myFunction is an asynchronous function that returns a promise that resolves with the value 'Hello, world'.

Await

The await keyword is used inside an async function to wait for the resolution of a promise.

await pauses the execution of the async function until the promise resolves or rejects.

async function myFunction() {
  let value = await Promise.resolve('Hello, world');
  console.log(value); // 'Hello, world'
}

myFunction();

In this example, await is used to wait for the promise to resolve, and then the resulting value is printed.

Basic Example

Let’s see it with a simple example,

async function getData() {
  //function that simulates returning a promise
  const response = await functionThatReturnsPromise();

  console.log(response);
}

In this example,

  • The getData function is asynchronous.
  • The functionThatReturnsPromise is a function that simulates returning a promise (it is the response from a network request).
  • We use the await keyword to wait for a promise to resolve before continuing with the next line of code.
  • After the promise resolves, we can do whatever we want with response.

This would be equivalent to this code, without using async and await:

function getData() {
  // Call the function that returns a promise
  functionThatReturnsPromise()
    .then((response) => {
      // Handle the response when the promise resolves
      console.log(response);
    })
    .catch((error) => {
      // Handle errors if the promise is rejected
      console.error(error);
    });
}

Which as we see is quite a hassle 😅

Internal Promises

An async function always returns a promise. This promise is resolved with the value returned from the function.

If an exception is thrown inside the async function, the promise is rejected with that exception.

async function successFunction() {
  return 'Success';
}

async function errorFunction() {
  throw new Error('Something went wrong');
}

successFunction().then(console.log); // 'Success'
errorFunction().catch(console.error); // 'Error: Something went wrong'

In successFunction,

  • The value 'Success' is wrapped in a promise that resolves.
  • In errorFunction, the thrown exception is wrapped in a promise that rejects.

Handling Errors

To handle errors in async functions, you can use try/catch instead of .catch() as you would with promises.

async function getData() {
  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();
    console.log(data);
  } catch (error) {
    console.error('Error:', error);
  }
}

getData();

In this example, if an error occurs at any point within the try block, the control flow moves to the catch block, where the error is handled.

Chaining Asynchronous Operations

You can chain multiple asynchronous operations within an async function.

async function processData() {
  let data = await getDataFromAPI();
  let processed = await processData(data);
  console.log(processed);
}

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

async function processData(data) {
  // Data processing
  return data.map(item => item * 2);
}

processData();

In this example, processData uses await to wait for the results of getDataFromAPI and processData in a sequence of operations.

Concurrent Functions

If you need to run multiple asynchronous operations in parallel, you can use Promise.all with await.

async function performOperations() {
  let promise1 = fetch('https://api.example.com/data1');
  let promise2 = fetch('https://api.example.com/data2');

  let responses = await Promise.all([promise1, promise2]);
  let data = await Promise.all(responses.map(res => res.json()));

  console.log(data);
}

performOperations();

In this case,

  • The two fetch requests are executed in parallel.
  • Promise.all waits for both to complete before continuing.