Fetch API: What, Why, and How

Just a few years back, the go-to method for making AJAX requests used to be XMLHttpRequest. But over time, the Fetch API has become an immensely popular method to fetch resources from the web.

Fetch is quite similar to XMLHttpRequest. The reason Fetch gained popularity over its predecessor is because it is more concise and is based on promises

JavaScript Promises

So what are promises anyway? JavaScript is a single-threaded programming language, which means it can only take care of one instruction at a time. However, we can make it work asynchronously with the help of callbacks. Sometimes this results in ‘callback hell’, which means ‘too many callbacks’ or ‘callbacks inside callbacks’. This makes our code messy and hard to follow. 

This is where Promise comes to the rescue.  A Promise is an object which assures us of future asynchronous task completion. 

A Promise can be in one of the following states:

  • pending: The initial state of promise, when it is neither fulfilled nor rejected.
  • fulfilled: meaning that the operation completed successfully.
  • rejected: meaning that the operation failed.
A basic Promise template
const promise = new Promise(function(resolve, reject) { 
    let a = "friend"; 
    let b = "friend"
    if(a === b) { 
        resolve(); 
    } else { 
        reject(); 
    } 
});  
promise.
    then(function () { 
        console.log('Yay! You are friends'); 
    }). 
    catch(function () { 
        console.log('Oops! error detected'); 
    }); 


Output: Yay! You are friends

A promise object is created using the new keyword. It takes a function as its parameter. This function is called an executor. The executor function is immediately executed when a promise is created.

The executor function takes two callback functions as its arguments: resolve and reject. The resolve callback is called when the task is completed successfully. On the other hand, the reject callback is called when the task encounters an error.

Now that we have a little background on promises, let’s move on to Fetch APIs.

A simple Fetch API template
fetch(URL)
.then(function(response) {
    if (!response.ok) {
        throw Error(response.statusText);
    }
    // Parse the response as JSON.
    return response.json();
    })
.then(function(data) {
    // Do stuff with the JSON
    console.log('Request succeeded with JSON response', data);
})
.catch(function(error) {
    console.log('Looks like there was a problem: \n', error);
});

The global fetch method, when called, returns a promise. We then wait for the promise to get resolved. When the promise is fulfilled, it returns a response object with a json() method. Before parsing the response as JSON, it is always a good idea to start by checking if the response status is 200. The response body is handled in the first then() method of the promise. 

The json() method also returns another promise which is handled in the second then() method which is chained to the first. And, in case of error catch() method gets executed.

Response Object methods

In addition to the json() method used in the previous example template, the response body supports various other methods as well. We can use those methods depending on what we want to do with the information. These methods include:

  • clone() – creates a clone of the response.
  • redirect() – creates a new response but with a different URL.
  • arrayBuffer() – returns a promise that resolves with an ArrayBuffer.
  • formData() – returns a promise but one that resolves with FormData object.
  • blob() – resolves with a Blob.
  • text() – resolves with a string.
  • json() – resolves the promise with JSON.

Let’s fetch some data from the GitHub API with json() method to see it in action:

fetch('https://api.github.com/users/1')
.then(function(response) {
    if (!response.ok) {
        throw Error(response.statusText);  
    } 
    return response.json();
})
.then(function(myJson) {
    console.log(
        `Name: ${myJson.name} 
        Id: ${myJson.id}
        Location: ${myJson.location}`
    );
    return myJson
 }) 
.catch(function(error) {
    console.log('Looks like there was a problem: \n', error);
});


Output:  "Name: Michael 
  	  Id: 1825798
 	  Location: San Francisco, CA”
Response Metadata

The response object that we get back from a fetch() call contains various pieces of information on the request. We can access those as follows:

fetch('https://api.github.com/users/1').then(response => {
    console.log(response.headers.get(‘ContentType’)); // application/json charset=utf-8"
    console.log(response.headers.get(‘Date’)); // null
    console.log(response.statusText); // "OK"
    console.log(response.type); // "cors"
    console.log(response.url); // "https://api.github.com/users/1"
})
Async-Await to fetch data

Async-await is the new alternative to ‘callbacks and promises’ for writing asynchronous code. It is better as it makes the code even more concise and clean, thereby making it easier to debug.

Fetching data with Async/Await:  Let’s use Async/Await to rewrite the GitHub API example shown earlier.

const fetchUsers = async () => {
    try {
        const response = await fetch('https://api.github.com/users/1');
        const data = await response.json();
        console.log(
            `Name: ${data.name} 
            Id: ${data.id}
            Location: ${data.location}`
        );
    } catch(e) {
        throw Error(e);
    }
}
fetchUsers();

Here we are creating an Async function. Then, we await the result of calling fetch() and get the value in a response variable. Next, we await again to retrieve the JSON from the response. We use the try-catch block to handle any failures. The catch block will be executed in case there is an error.

Post data with fetch()

So far we have only been getting data through the fetch() method. But fetch() is capable of doing much more. We can use all the other kind of requests such as POST, PUT, DELETE, OPTIONS, send additional information or change headers according to our need. All we need to do is create an object and set the object as the second parameter to the fetch() method.

fetch('https://api.github.com/users/1', {
    method: 'post',
    headers: {
        "Content-type": "application/json; charset=UTF-8"
    },
    body: 'foo=bar&lorem=ipsum'
})
.then(function(response) {
    return response.json();
})
.then(function (data) {
    console.log('Request succeeded with JSON response', data);
})
.catch(function (error) {
    console.log('Request failed', error);
});

We can also use the request constructor to create our object. We do that by using the new keyword and pass the request as a parameter to the fetch method. Let’s take a look at the example below:

const url = 'https://api.github.com/users/1';
// The data we are going to send in our request
let data = {
    name: 'Tom',
}
// Create the request constructor with all the data
var request = new Request(url, {
    method: 'POST', 
    body: data, 
});
fetch(request)
.then(function(response) {
    return response.json();
})
.then(function (data) {
    console.log('Request succeeded with JSON response', data);
})
.catch(function (error) {
    console.log('Request failed', error);
});

Do give Fetch API a try. This article just scratched the surface of this handy API. Here are some additional resources: MDN Docs, Google Developers and David Walsh’s tutorial on fetch.

Thanks for reading!

1
0

Related Posts