Demystifying JavaScript Asynchronous Operations: Promises vs. Async/Await

Sugand singh
5 min readAug 26, 2023

--

Introduction

In the world of JavaScript programming, asynchronous operations play a pivotal role in creating responsive and efficient applications. Developers often encounter scenarios where tasks need to be performed concurrently without blocking the main thread. Two popular approaches for managing asynchronous code are Promises and Async/Await. In this article, we’ll dive into the key differences between these two techniques.

Promises: A Foundation for Asynchronous Operations

Promises were introduced in ECMAScript 6 (ES6) and were a significant step forward in handling asynchronous code. A Promise is an object representing the eventual completion or failure of an asynchronous operation. It provides a clean way to manage asynchronous tasks, making code more readable and maintainable.

A Promise has three states:

  1. Pending: The initial state, representing the asynchronous operation that hasn't been completed yet.
  2. Fulfilled/Resolved: The operation was completed successfully, and the promise has value.
  3. Rejected: The operation failed, and the promise holds a reason for the failure.

The basic structure of a Promise looks like this:

const myPromise = new Promise((resolve, reject) => {
// Asynchronous operation
if (/* operation successful */) {
resolve(result);
} else {
reject(error);
}
});

In this code, a Promise is created using the new Promise() constructor. The constructor takes a function with two parameters: resolve and reject. These parameters are themselves functions that you can call to signal the successful completion or failure of the asynchronous operation encapsulated within the Promise.

  • If the asynchronous operation is successful, you call resolve(result) whereresult is the value that the Promise will fulfill with.
  • If the asynchronous operation encounters an error, you call reject(error) whereerror is the reason or error object that the Promise will be rejected with.

Here’s how you would use this Promise to handle its fulfillment or rejection:

myPromise
.then(result => {
Handle the successful result
console.log("Operation successful:", result);
})
.catch(error => {
// Handle the error
console.error("An error occurred:", error);
});

In this code,

  • The.then() method is called Promise. It takes a function that will be executed if the Promise is fulfilled (i.e., the resolve function was called). Inside this function, you can handle the result of the successful operation.
  • The.catch() method is called Promise. It takes a function that will be executed if the Promise is rejected (i.e., the reject function was called). Inside this function, you can handle the error that occurred during the operation.

Async/Await: A Syntactic Sweetener

While Promises were a significant improvement over traditional callback-based approaches, they could still lead to complex and nested code structures. To address this, Async/Await was introduced in ES2017 as a more intuitive way to work with asynchronous code.

Async functions are functions that always return a Promise. Within an async function, you can use the await keyword to pause the execution of the function until the Promise is resolved, allowing for a more sequential coding style.

Here’s an example of using Async/Await:

async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (error) {
throw new Error('Failed to fetch data');
}
}

fetchData()
.then(data => {
// Use the fetched data
})
.catch(error => {
// Handle the error
});

In this code, you’ve defined an asynchronous function named fetchData(). Let's break down the steps:

  1. async function fetchData() { ... }This declares an asynchronous function named fetchData. Theasync keyword indicates that this function will use the await keyword inside it to handle asynchronous operations.
  2. try { ... } catch (error) { ... }Inside thefetchData function, you're using a try-catch block. This allows you to handle errors that might occur during the asynchronous operations in a structured way.
  3. const response = await fetch('https://api.example.com/data');The awaitkeyword is used to pause the execution of the function until the asynchronous operation inside it (in this case, fetching data from a URL) is complete. Thefetch function returns a Promise that resolves to the response from the URL.
  4. const data = await response.json();Here, you're awaiting the JSON parsing of the response. This also involves another asynchronous operation because parsing the response body as JSON is itself an asynchronous task.
  5. return data;After successfully fetching and parsing the JSON data, you return the data from the function.
  6. catch (error) { ... }If any error occurs during the try block (like network errors, JSON parsing errors, etc.), the catch block will be executed. Inside the catch block, you're throwing a new error with the message 'Failed to fetch data'.
  7. fetchData().then(data => { ... }).catch(error => { ... });You're calling thefetchData function, which returns a Promise. You're then chaining a.then() block to handle the successful result (thedata) and a.catch() block to handle any errors that occur during the Promise's lifecycle.

Keep in mind that the .then() and .catch() blocks inside thefetchData() function are placeholders in your code. You would typically add code within those blocks to actually use the fetched data or handle the error appropriately.

This code utilizes the power of async and await to write asynchronous code in a more sequential and readable manner. It encapsulates asynchronous operations and error handling in a cleaner structure, enhancing the maintainability of your code.

Key Differences and Considerations

  1. Readability: Async/Await tends to result in cleaner and more readable code, especially when dealing with multiple asynchronous operations.
  2. Error Handling: With Promises, you primarily use .then() and .catch() for error handling. In Async/Await, you can use traditional try...catch blocks.
  3. Error Stacks: Promises generally provide better error stack traces compared to Async or Await, making debugging easier.
  4. Error Bubbling: Promises automatically propagate errors to the nearest thread.catch(), while with Async/Await, you need to explicitly handle errors.
  5. Sequential vs. Parallel: Promises inherently allow parallel execution of asynchronous tasks, while Async/Await promotes sequential execution.
  6. Compatibility: Promises have broader compatibility since they are available in older JavaScript environments. Async/Await might require transpilation for older browsers.

Conclusion

Promises and Async/Await are both powerful tools for managing asynchronous operations in JavaScript. The choice between them often comes down to personal preference and the complexity of your code. Promises are a solid choice for projects targeting older environments or when parallel execution is crucial. On the other hand, Async/Await provides a more intuitive and sequential way of writing asynchronous code, enhancing code readability and maintainability. Whichever you choose, mastering these techniques will undoubtedly make you a more effective and proficient JavaScript developer.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Sugand singh
Sugand singh

Written by Sugand singh

Experienced React Native dev, UI/UX expert, performance optimizer. Keeping up with mobile trends. Let's build mobile magic!

Responses (1)

Write a response