JavaScript Promises for Beginners

Promises, States of promises, then-catch, async-await, try-catch

JavaScript Promises for Beginners

Let's pretend you're in a hypothetical situation where you've planned your birthday party, invited your friends around, and are preparing delicious food when you notice you're missing one ingredient.

You dispatch someone to collect the ingredient, but you have no idea whether it will be accessible in the market. So, because you're a smart person, you swiftly decide that if the ingredient can be acquired, you'll stick with the same recipe, but if it can't, you'll prepare something else. As a result, the final recipe is dependent on the ingredient's availability.

Let's look at a scenario that occurs frequently in web development. We're making an API request to a server. We're looking for API data from the server, but we're not sure whether or not we'll get it, or when we'll get it. So, similar to the previous example, we can plan what we'll do if the API request is successful and what we'll do if it fails.

This is where JavaScript Promises comes into the picture.

Promises

Let's take a look at what MDN has to say about the Promise.

"The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value." - MDN

In Js, we assign a Promise to every async operation, and after the async operation completes, the Promise object is updated with the outcome of the async operation.

So, every async operation returns a Promise, which is updated after the async operation is complete, depending on whether the async operation was successful or not.

States of Promises -

Depending upon the current status of the async operation, a Promise can have four states.

States of Promise -

  • Pending
  • Fulfilled
  • Rejected
  • Settled

Pending Promise - The Promise is in the Pending state when an async action has begun but has not yet been completed.

Fulfilled Promise - When the async action is complete and successful, the Promise is said to be in the Fulfilled state.

Rejected Promise - When the async operation is completed but failed, the Promise is said to be in the Rejected state.

Settled Promise - The async operation is considered to be in a settled state when it is completed, regardless of whether it was successful or not.

image.png

then & catch

When a Promise is resolved, the then and catch statements are executed, depending on whether the Promise is fulfilled or rejected.

fetch("Some API data")
.then(response => saveResponse(response))
.catch(err => alertUser(err))

Fetch is the Js method that returns a Promise, if that Promise is fulfilled then the .then statement will be executed. But if the Promise is rejected then .catch will be executed.

You can chain several then statements to make several asynchronous tasks one after another.

fetch("Data from API One")
.then(res => fetch("Data from API Two")
.then(res => saveToDatabase(res))
.catch(err => alertUser(err))

The catch statement will run if any on the Promise is rejected. You can chain multiple promises one after another using then and catch statement.

As you may have noticed the syntax of then and catch becomes confusing when we do multiple Promise chaining. So to make the Promise more developer-friendly, async-await was introduced, it is just syntactical sugar over the then & catch statement.

Async-Await

We must first declare a function using the async keyword in order to use async-await. Only within this async function can you utilize the await keyword. The code below the await function will only run when the Promise-related await keyword has been settled in this function. If the Promise is fulfilled, then the code below the await keyword will be executed.

const fetchApiData = async () => {
  const response = await fetch("Some API data")
  saveToDatabase(response)
}

Using async-await provides good readability to the code since it makes the asynchronous code appear as if they are synchronous.

But what about handling rejected Promises when using async-await?

Try-catch

Try and catch statements are used in conjunction. Promises reside inside the try code-block and if any of the promises fail to be fulfilled then the catch code-block is executed.

const fetchApiData = async () => {
  try{
    // Promises
  }
  catch(err){
    // If Promise gets rejected  
  }
}

As a result, switching the previous then-catch statement code to the async-await function will be -

const fetchApiData = async () => {
  try{
     const responseOne = await fetch("Data from Api One")
     const responseTwo = await fetch("Data from Api Two")
     saveToDatabase(responseTwo)
  }
  catch(err){
     alertUser(err)
  }
}

In the above async function the second Promise fetch("Data from Api Two") will execute only after the first Promise has been fulfilled. If any of the Promise get rejected then the code inside the catch block will run.