Understanding JavaScript Promises and Async/Await

In a synchronous environment, code is executed one line at a time, and the next line of code doesn't execute until the current line has finished. This can cause delays, or even freezing, in web applications when dealing with time-consuming operations like network requests or reading from a file.

Understanding JavaScript Promises and Async/Await
Photo by Semyon Borisov / Unsplash

JavaScript, being one of the most popular programming languages in the world, has evolved over the years to address the needs of the modern web. Among its many features, one that has truly transformed the way developers handle asynchronous code is the introduction of Promises and the Async/Await syntax. This blog post aims to provide a comprehensive guide to understanding and mastering these powerful tools.

Synchronous vs. Asynchronous JavaScript

In a synchronous environment, code is executed one line at a time, and the next line of code doesn't execute until the current line has finished. This can cause delays, or even freezing, in web applications when dealing with time-consuming operations like network requests or reading from a file.

To prevent these issues, JavaScript employs an asynchronous execution model. With asynchronous code, an operation can be initiated without waiting for it to complete. This enables the program to continue executing other tasks while the operation is being processed in the background.

Understanding Promises

A Promise is a JavaScript object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It allows you to attach callbacks that will be executed once the operation is complete, instead of passing them as arguments into a function.

Creating a Promise

A Promise is created using the Promise constructor, which takes a single argument, a function called the "executor." The executor function takes two arguments: a resolve function and a reject function. The resolve function is used to fulfill the Promise with a resulting value, while the reject function is used to reject the Promise with a reason.

const promise = new Promise((resolve, reject) => {
  // Asynchronous operation
});

Promise States

A Promise can be in one of three states:

  • Pending: The initial state; neither fulfilled nor rejected.
  • Fulfilled: The operation completed successfully, and the Promise has a resulting value.
  • Rejected: The operation failed, and the Promise has a reason for the failure.

Promise Chaining

Promises can be chained together using the then and catch methods. The then method is used to attach callbacks that will be called when the Promise is fulfilled, while the catch method is used to attach callbacks that will be called when the Promise is rejected. Both methods return a new Promise, allowing for further chaining.

promise
  .then((result) => {
    // Handle the result
  })
  .catch((error) => {
    // Handle the error
  });

Async/Await Syntax

Introduced in ECMAScript 2017, the async/await syntax provides a more concise and readable way to work with Promises. It allows you to write asynchronous code that looks and behaves like synchronous code.

Declaring Async Functions

To use the async/await syntax, you need to declare a function as asynchronous by adding the async keyword before the function keyword or the arrow function.

async function fetchData() {
  // ...
}

const fetchData = async () => {
  // ...
};

Using Await

Inside an async function, you can use the await keyword to pause the execution of the function until a Promise is fulfilled. The await keyword can only be used within an async function and will return the resolved value of the Promise.

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

Error Handling with Async/Await

Handling errors in async/await functions can be done using the traditional try and catch blocks. When an error occurs within the try block, the execution jumps to the corresponding catch block, allowing you to handle the error gracefully.

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

Practical Examples and Use Cases

Let's look at some practical examples that demonstrate the power of Promises and async/await in real-world applications.

Fetching Data from an API

Using the Fetch API, we can retrieve data from a remote server and display it in our application. With async/await, this process becomes more streamlined and easier to read.

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

function displayData(data) {
  // Render data in the UI
}

Promise.all()

Promise.all() is a useful method that takes an array of Promises and returns a new Promise that is fulfilled with an array of the resolved values, in the same order as the input Promises. This is particularly helpful when you need to perform multiple asynchronous operations concurrently and wait for all of them to complete.

async function fetchMultipleData() {
  try {
    const urls = ['https://api.example.com/data1', 'https://api.example.com/data2'];
    const requests = urls.map((url) => fetch(url));
    const responses = await Promise.all(requests);
    const data = await Promise.all(responses.map((response) => response.json()));
    console.log(data);
  } catch (error) {
    console.error('Error fetching multiple data:', error);
  }
}

Promises and the async/await syntax have revolutionized the way developers handle asynchronous code in JavaScript, making it more readable and maintainable. By understanding and mastering these concepts, you'll be better equipped to build more robust and efficient web applications.

Keep practicing with real-world examples and exploring more advanced use cases to enhance your skills and become a JavaScript Promises and async/await expert.

About the author

Joff Tiquez, hailing from Manila, Philippines, is the individual behind the establishment of OSSPH. He is a web developer who strongly supports open source and has been overseeing projects like Vue Stripe for an extended period. To get in touch with Joff, you can visit https://bento.me/jofftiquez.