Understanding Promises and the Fetch API in JavaScript.

Beginner friendly explanation of Promises leading on to how Fetch API works using Promises

Understanding Promises and the Fetch API in JavaScript.

What is a Promise?

A Promise is an object that is typically used to handle asynchronous operations such as network calls. Promises work by giving us an indication of the outcome of those asynchronous operations and initiating a response based on the outcome. At any given time a Promise is in one of the 4 states . Based on whether the asynchronous operations executed successfully or got rejected promise calls the appropriate function which is designed to handle each case.

The 4 states of a Promise

  1. fulfilled: Indicates that the asynchronous operations have been carried out successfully.
  2. rejected: Indicates that the asynchronous operations have failed.
  3. pending: Indicates that the operation is/are still being carried out meaning the promise is neither fulfilled nor in the rejected state.
  4. settled: The promise has either fulfilled or rejected.

promiseStates.jpg

Based on whether the promise has been fulfilled or rejected we can execute an appropriate function to handle that state.

To understand this better, consider the following scenario:

You are making a call to an API requesting some data, the API returns the expected data. This indicates that your call was successful. If the API didn't respond to your request then that indicates that the call failed.

Based on whether your call was successful or a failure you will have to handle further execution of your code differently.

This is where a Promise helps us, it indicates whether your network call or any operation for that matter has been executed successfully or failed, and based on that indication it also does the job of calling appropriate function for both the scenarios.

How to create a Promise?

To create a Promise object, a new keyword followed by the Promise() constructor function is used. The promise constructor function takes a function as its parameter. This function is called the executor function.

const promise = new Promise((resolve, reject) => {
  //run any operations
});

What is the executor function?

The callback function (resolve, reject) => {} which is passed in Promise() in the above code snippet is the executor function.

When we create a new Promise object, two functions resolve and reject get created along with it and passed on to the executor function as parameters.

Inside executor function we define our operations typically asynchronous ones, then if our operations get executed successfully we indicate that by calling the resolve function, and if they failed we indicate that by calling the reject function.

Let's see a simple Promise example to understand it better:

const promise = new Promise((resolve, reject) => {
    // executing any operation...
    const a = 5 * 10;

    // Based on the output of our operation deciding 
   //whether to indicate success or failure;
    if (a === 50) {
      const successMessage = "Promise resolved";
      resolve(successMessage);
    } else {
      const failureMessage = "Promise rejected";
      reject(failureMessage);
    }
  });

The .then() and the .catch() method

Now that we have created our Promise, the next part is to run appropriate code based on fulfillment or rejection of our promise aka handling success or rejection.

The Promise object provides us with .then() and .catch() method that handle success or failure respectively.

The .then() and .catch() method both take a callback function. When the Promise is resolved the callback function inside the .then() method gets called, the value that was passed into resolve() while invoking it gets passed in this callback function as a parameter.

Similarly, when the Promise is rejected the callback function inside the .catch() method gets called, and the value that was passed into reject() while invoking it gets passed in this callback function as a parameter.

Now let's see an example tying everything we learnt about Promises.

const promise = new Promise((resolve, reject) => {
    // executing any operation...
    const a = 5 * 10;

    // Based on the output of our operation 
   //deciding whether to indicate success or failure;
    if (a === 50) {
      const successMessage = "Promise resolved";
      resolve(successMessage);
    } else {
      const failureMessage = "Promise rejected";
      reject(failureMessage);
    }
  });

//handling the fulfillment or rejection 
  promise
    .then((successMessage) => console.log("From callback func inside then method: " + successMessage))
    .catch((failureMessage) => console.log("From callback func inside catch method: " + failureMessage));

Output:

"From callback func inside then method: Promise resolved"

How Fetch API works?

fetchdog.jpg

Now that we know how a Promise works let's understand how fetch() works

Consider the following example:

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => response.json())
  .then(json => console.log(json))
  .catch(error => console.log("something went wrong: " + error) )

In the above code, we pass a URL to fetch() . Internally fetch() makes a network call using that url. If the request has been resolved successfully the callback function inside then() method i.e. response => response.json() gets called and whatever was the response of that network call gets passed as a parameter in this callback function.

Now there is an unusual thing over here, there are two .then methods chained one after the other. The reason behind it is that the response that was received by our callback function response => response.json() is not suitable for our use. Our goal is to only get data in JSON format hence as you may have noticed, we have called .json() on response in our callback function which will help us in extracting data in JSON format from the response.

Now when we called .json() method, the .json() method itself returns a Promise. So now we again have to do the same thing for this promise as well, i.e. call .then() method for handling success of this new Promise.

Hence, we have chained one more .then() after the first .then().

Notice, how there is only one .catch(), we don't need to call .catch() separately for every Promise it gets handled by the same one.

Like this, we can chain n number of Promises one after the other and the code will still remain readable. This is one of the major advantages of Promises, they prevent the problem of the infamous callback hell by allowing us to chain multiple promises.

Hope this blog helped you in understanding Promise and the fetch() API.

If you have any questions or suggestions find me on Twitter