Tricks which ES6 feature should we use in javascript promises vs async await?

 Asynchronous programming is crucial for efficient web development, and JavaScript provides two popular approaches: Promises and Async/Await. In this blog post, we'll explore the differences between Promises and Async/Await and guide you on when to use each technique. By understanding their strengths, you'll be able to make informed decisions and write more maintainable code.

1. Understanding Promises:

Promises are a core feature of JavaScript, enabling elegant handling of asynchronous operations. They represent the eventual completion or failure of an asynchronous task and offer methods like .then(), .catch(), and .finally() for handling results.

Promises have three states:

  1. Pending: The initial state when a promise is created and hasn't been resolved or rejected.
  2. Fulfilled: The state when a promise is successfully resolved with a value.
  3. Rejected: The state when a promise fails to fulfill its intended operation, typically accompanied by a reason for rejection.
Creating Promises:

Promises can be created using the Promise constructor, which takes a single function called the executor. The executor function has two arguments: resolve and reject. Let's see an example:

const myPromise = new Promise((resolve, reject) = {
  
  // Asynchronous operation (API call)
    const result = Math.random();
    if (result > 0.5) {
      resolve(result);
    } else {
      reject(new Error('Something went wrong!'));
    }
});

myPromise
.then((value) = {
	console.log('Promise fulfilled with value:', value);
 })
.catch((error) = { 
	console.error('Promise rejected with error:', error);
 });
Once a promise is created, we can consume its value using the then and catch methods. The then method is called when a promise is fulfilled, while the catch method is used to handle rejections.

Promise Chaining:
Promises can be chained together, allowing us to perform a sequence of asynchronous operations one after another. The return value of a then or catch method call is another promise, which enables method chaining. Let's look at an example:

const fetchUserData = () = {
  return fetch('https://abcd.com/users/1')
  		.then((result) = {
  			return result.json().userData
  })
};

const fetchUserPosts = (userId) = {
	return fetch('https://abcd.com/posts/${userId}')
		.then((result) = {
            return result.json().userPosts
  })
};

fetchUserData()
  .then((userData) => {
    console.log('User data:', userData);
    return fetchUserPosts(userData.userId);
  })
  .then((userPosts) => {
    console.log('User posts:', userPosts);
    console.log('Total posts:', userPosts.length);
  })
  .catch((error) => {
    console.error('An error occurred:', error);
  });

In the code above, we define two functions, fetchUserData and fetchUserPosts,which simulate fetching user data and user posts respectively. We chain the promises by calling then on the first promise returned by fetchUserData. Within the then callback, we log the retrieved user data and return a new promise by calling fetchUserPosts with the userId from the previous promise's resolved value. In the second then callback, we log the retrieved user posts and perform any further processing.

By chaining promises, we ensure that the second API call ( fetchUserPosts ) is made only after the first API call ( fetchUserData ) successfully completes. This sequential execution allows us to handle data in a structured and predictable manner.

2. Understanding Async/Await:

Async/Await, introduced in ECMAScript 2017 (ES8), simplifies handling of asynchronous operations by providing a more synchronous-looking syntax. It builds upon Promises and uses the async and await keywords to pause execution until a Promise resolves.

Using async/await involves two keywords:

  1. async: It is used to define an asynchronous function. An async function always returns a promise, which resolves to the value returned by the function or rejects with any thrown exception.
  2. await: It is used to pause the execution of an async function until a promise is resolved. The await keyword can only be used inside an async function.

Syntax and Usage: To use async/await, follow these steps:

1. Declare an async function:

async function fetchData() {
  // Asynchronous operations
}

2. Use await to pause the execution and retrieve the resolved value of a promise:

async function fetchData() {
  const result = await somePromise;
  // Continue execution with the resolved value
}

3. Handle errors using traditional try/catch blocks:

async function fetchData() {
  try {
    const result = await somePromise;
    // Continue execution with the resolved value
  } catch (error) {
    // Handle the error
  }
}

We will check out the same example of `fetchUserData` & `fetchUserPosts` explained in promise chaining above with async/await.

Example:

const fetchUserData = async () = {
  const response = await fetch('https://abcd.com/users/1');
  const userData = await response.json().userData;
  return userData;
};

const fetchUserPosts = async (userId) = {
  const response = await fetch(`https://abcd.com/posts/${userId}`);
  const userPosts = await response.json().userPosts;
  return userPosts;
};

const fetchUserDetails = async () = {
  const userData = await fetchUserData();
  const userPosts = await fetchUserPosts(userData.userId);
  
  console.log('User Data:', userData);
  console.log('User posts:', userPosts);
  console.log('Total posts:', userPosts.length);
}
Benefits of async/await:

Let's delve into the advantages of using async/await for asynchronous programming:

  1. Improved Readability: By mimicking synchronous code structure, async/await makes asynchronous code easier to read and understand. The sequential flow of await statements gives the impression of synchronous execution, resulting in more readable and maintainable code. This is especially beneficial when dealing with complex logic involving multiple asynchronous operations.

  2. Simplified Error Handling: async/await simplifies error handling by allowing the use of familiar try/catch blocks. Error handling becomes more straightforward, as exceptions thrown inside the async function can be caught and handled within the same scope. This leads to cleaner and more maintainable code, compared to attaching multiple .catch() handlers in promise chaining.

  3. Control Flow Management: Using await allows precise control over the flow of asynchronous operations. The execution of an async function halts at each await statement until the awaited promise resolves or rejects. This enables better control over dependencies between asynchronous tasks, allowing you to write logic that executes sequentially, making the code more predictable and manageable.

  4. Enhanced Debugging Experience: One significant advantage of async/await is improved debugging capabilities. When an error occurs, the error stack trace remains intact and accurately points to the location where the error was thrown. This makes it easier to identify and fix issues, as the stack trace provides more informative and relevant information.

  5. Compatibility with Existing Synchronous Code: async/await can be seamlessly integrated into existing synchronous codebases. You can mix synchronous and asynchronous operations within an async function, allowing you to gradually introduce asynchronous code without significant refactoring. This compatibility makes the adoption of async/await more convenient for developers.

Handling Multiple Promises:
There are scenarios where we need to work with multiple promises simultaneously and wait for all of them to fulfill. JavaScript provides the Promise.all method for this purpose.
Let's see an example:

const promise1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('First Promise');
  }, 2000);
});

const promise2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('Second Promise');
  }, 1000);
});

Promise.all([promise1, promise2])
  .then((results) => {
    console.log('All promises resolved:', results);
  })
  .catch((error) => {
    console.error('An error occurred:', error);
  });

In the example above, we create two promises ( promise1 and promise2 ) that resolve after different time intervals. By passing an array of promises to Promise.all, we ensure that both promises are executed concurrently. The then method is called once all the promises are successfully resolved, providing an array of resolved values ( results in the example).

It's important to note that if any promise in the input array is rejected, the entire Promise.all operation will be rejected. This behavior ensures that all asynchronous operations complete successfully before continuing.

Use Cases for Promise.all:

  1. Parallel API Requests: Promise.all is particularly useful when making parallel API requests. Instead of waiting for each request to complete individually, you can fire off multiple requests simultaneously and wait for all of them to resolve before processing the aggregated data.

  2. Data Aggregation: If you need to gather data from multiple sources or perform complex calculations on independent data sets, Promise.all allows you to execute those tasks concurrently. Once all the promises are resolved, you can consolidate and process the data in a unified manner.

  3. Dependent Asynchronous Operations: In some cases, you may have multiple asynchronous operations where one depends on the result of another. Promise.all can be utilized to ensure that all prerequisite operations are completed before proceeding further.

  4. Performance Optimization: By executing multiple asynchronous tasks simultaneously using Promise.all you can optimize the performance of your application, as it reduces the overall execution time compared to running tasks sequentially.

Conclusion:

Promises and Async/Await are powerful tools for managing asynchronous JavaScript operations. Promises excel in granular control, while Async/Await prioritizes readability. By understanding their strengths, you can choose the right tool for your specific project needs. Implementing them correctly enhances code quality and improves the overall efficiency of your web development

- Nishad Shirsat

Comments

Popular post

React Lifecycle in Functional Components with useEffect

Mastering JavaScript Variable Declarations: A Comprehensive Guide to let, var, and const difference