A Beginner's Journey into Asynchronous JavaScript: Promises and Async/Await Explained 🤞🔥

A Beginner's Journey into Asynchronous JavaScript: Promises and Async/Await Explained 🤞🔥

Hello developers, If you’ve ever worked with JavaScript, you probably know that it’s a language that supports asynchronous programming. This means that you can write code that doesn’t block the execution of other code and can handle multiple tasks at the same time.

But how do you write asynchronous code in JavaScript? And how do you manage the complexity and avoid the pitfalls of callback hell?

In this blog post, I’ll explain two concepts that can help you write cleaner and more readable asynchronous code in JavaScript: promises and async-await.

What are promises?🤔

A promise is an object that represents the result of an asynchronous operation. It can be in one of three states:

  • Pending: The operation is not yet completed, and the promise is waiting for the result.

  • Fulfilled: The operation is completed successfully, and the promise has value.

  • Rejected: The operation is completed with an error, and the promise has a reason.

You can think of a promise as a placeholder for a future value. It’s like saying “I promise to give you something later, but I don’t know what it is or when it will be ready”.

For example, let’s say you want to order a pizza online. You fill out the form, click the order button, and wait for the confirmation. The confirmation is a promise: it tells you that your order has been received, but it doesn’t tell you when your pizza will be delivered or if there will be any issues.

Inside the Promise: States and Execution Flow🧐

A promise has two main components: an executor function and a handler function.

The executor function is the function that creates the promise and performs the asynchronous operation. It has two parameters: resolve and reject. These are functions that you can use to either fulfill or reject the promise.

The handler functions are the functions that handle the result of the promise. They are attached to the promise using the .then() and .catch() methods. The .then() method takes two functions as arguments: one for the fulfillment case, and one for the rejection case. The .catch() method takes one function as an argument, which handles any rejection that occurs in the promise chain.

Promise in JavaScript | Board Infinity

  1. Creating a Promise🔬

You can create a promise using the new Promise() constructor, which takes an executor function as an argument. This function is called immediately when the promise is created, and it has access to the resolve and reject functions.

For example, let’s say we want to create a promise that simulates a network request. We can use the setTimeout() function to mimic a delay, and then call either resolve or reject depending on some conditions.

const promise = new Promise((resolve, reject) => {
  // Perform asynchronous task here
  // If successful, call resolve(result)
  // If there's an error, call reject(error)
});
  1. Resolving and Rejecting Promises:

    To resolve a Promise with a value, call the resolve function inside the executor. If there's an error or the operation fails, call the reject function with the appropriate error.

const fetchUserData = () => {
  return new Promise((resolve, reject) => {
    fetch('https://api.example.com/users')
      .then((response) => response.json())
      .then((data) => resolve(data))
      .catch((error) => reject(error));
  });
};
  1. Handling Promise Results:

    We can handle the results of a Promise using the then() method, which is called when the Promise is resolved successfully. To handle errors, use the catch() method.

fetchUserData()
  .then((data) => {
    console.log('User data:', data);
  })
  .catch((error) => {
    console.error('Error fetching user data:', error);
  });
  1. Chaining Promises:

Promises can be chained using multiple then() calls. This is useful when you need to perform sequential asynchronous operations.

const promise1 = new Promise((resolve, reject) => {
  // Do something asynchronous
  resolve('The first promise is resolved!');
});

const promise2 = new Promise((resolve, reject) => {
  // Do something asynchronous
  resolve('The second promise is resolved!');
});

const promise3 = new Promise((resolve, reject) => {
  // Do something asynchronous
  resolve('The third promise is resolved!');
});

promise1
  .then(() => promise2)
  .then(() => promise3)
  .then((value) => {
    // Do something with the value of the third promise
  });

When this code is executed, the first promise is resolved. The then() method on the first promise then calls the then() method on the second promise, passing in the value that the first promise was resolved with. The then() method on the second promise then calls the then() method on the third promise, passing in the value that the second promise was resolved with. Finally, the then() method on the third promise calls the callback function that was passed in, passing in the value that the third promise was resolved with.

fetchUserData()
  .then((data) => {
    // Process the user data and return some value
    return processUserData(data);
  })
  .then((processedData) => {
    // Further process the data or make another API call
    return someOtherTask(processedData);
  })
  .then((finalResult) => {
    console.log('Final result:', finalResult);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

iT 邦幫忙::一起幫忙解決難題,拯救IT 人的一天

Understanding Async/Await in JavaScript

What is Async/Await?

Async/await is a new syntax in JavaScript that makes it easier to write asynchronous code. Async/await allows you to write code that looks like synchronous code, but that actually runs asynchronously.

How Async/Await Works?

To use async/await, a function must be marked with the async keyword. Inside an async function, you can use the await keyword to pause the function execution until a Promise is resolved. This makes the code appear more synchronous, improving its readability.

async function getData() {
  try {
    const result = await someAsyncFunction();
    console.log('Result:', result);
  } catch (error) {
    console.error('Error:', error);
  }
}

Error Handling with Async/Await:

We use the try...catch block to handle errors in async/await functions. If a Promise is rejected or an error occurs, the catch block will handle it gracefully.

async function getData() {
  try {
    const result = await someAsyncFunction();
    console.log('Result:', result);
  } catch (error) {
    console.error('Error:', error);
  }
}

Combining Async/Await with Promise Chaining:

Async/await can be used in combination with Promise chaining to perform complex asynchronous operations in a more structured way.

async function processUser() {
  try {
    const userData = await fetchUserData();
    const processedData = await processUserData(userData);
    const finalResult = await someOtherTask(processedData);
    console.log('Final result:', finalResult);
  } catch (error) {
    console.error('Error:', error);
  }
}

The processUser() function is an async function that utilizes the power of await to handle asynchronous operations in a synchronous-like manner. It demonstrates the use of await with multiple asynchronous functions to process user data step by step. Let's break down the code and understand how it works:

  1. async function processUser() {

    • The function is declared as an async function, indicating that it contains asynchronous operations and will return a Promise implicitly.
  2. try {

    • We start a try block to handle potential errors that might occur during the asynchronous operations.
  3. const userData = await fetchUserData();

    • The first await statement pauses the execution of processUser() until the Promise returned by fetchUserData() is resolved or rejected. Once the Promise is resolved, the result (user data) is stored in the variable userData.
  4. const processedData = await processUserData(userData);

    • The second await statement pauses the execution again until the Promise returned by processUserData(userData) is resolved or rejected. The userData obtained from the previous step is passed as an argument. The result of this operation is stored in the variable processedData.
  5. const finalResult = await someOtherTask(processedData);

    • The third await statement pauses the execution again until the Promise returned by someOtherTask(processedData) is resolved or rejected. The processedData obtained from the previous step is passed as an argument. The result of this operation is stored in the variable finalResult.
  6. console.log('Final result:', finalResult);

    • After all the asynchronous operations are successfully completed, the final result is logged to the console.
  7. } catch (error) {

    • If any error occurs during the await operations within the try block, the execution jumps to the corresponding catch block.
  8. console.error('Error:', error);

    • The catch block is executed when there's an error in any of the awaited operations. The error is logged to the console using console.error().

By using await, we avoid the need to nest multiple .then() calls, which can lead to callback hell. Instead, the code is organized in a more linear and readable manner, making it easier to understand the flow of asynchronous operations. The try...catch block ensures that any errors occurring during the asynchronous tasks are handled gracefully without crashing the program.

Remember that to use await, the containing function must be marked as async. Async functions always return a Promise, either explicitly or implicitly. In this case, processUser() implicitly returns a Promise that resolves when all the asynchronous operations inside it are completed, or rejects if any of the awaited Promises reject.

Conclusion:

Promises and Async/Await are powerful tools in JavaScript for dealing with asynchronous operations. Promises simplify handling asynchronous tasks by providing a structured approach to managing their resolution and rejection. On the other hand, Async/Await takes it a step further by making the syntax more concise and synchronous-looking.

Choose the approach that suits your coding style and project requirements.

Fuel Your Passion! 🚀 Discover new horizons in our upcoming blog!