Introduction to the JavaScript Fetch API

Compared to XMLHttpRequest and the libraries built around it, like JQuery.ajax, the fetch API defines a more modern and cleaner way of performing asynchronous requests, based on the use of promises. In this article we will see some of the interfaces provided by the API, like Request and Response, and we will learn how to use the fetch method to perform various types of asynchronous requests.

In this tutorial you will learn:

  • How to send asynchronous requests using the fetch method
  • How to work with the Request and Response objects provided by the fetch API

Software Requirements and Conventions Used

Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Os-independent
Software A browser supporting the Fetch API or the node-fetch package if working with nodejs
Other Knowledge of modern javascript features like promises and arrow functions
Conventions # – requires given linux commands to be executed with root privileges either directly as a root user or by use of sudo command
$ – requires given linux commands to be executed as a regular non-privileged user

Basic usage

The Fetch API represents HTTP requests and responses using Request and Response interfaces and provides the fetch method to send requests asynchronously. Let’s start from a really basic example of how to use it.



The fetch method has only one mandatory argument, which is either the path of the resource to be fetched or a Request object. When only this parameter is passed to the function, a GET request is performed to retrieve the specified resource. For the sake of this example, we will use the NASA API call which returns information about the astronomic “picture of the day” in JSON format. Here is our code:

fetch('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY')
  .then(response => response.json())
  .then(json_object => console.log(json_object))
  .catch(reason => console.log(reason))

Let’s briefly explain how the code above works.The fetch function returns a promise: if said promise is fulfilled, it resolves to a Response object which represents the HTTP response to the request we sent.

The then method of the promise object is called when the promise exists the pending state. Let’s remember that the method returns itself a new promise, and accepts up to two callbacks as its arguments: the first is called if the promise is fulfilled; the second if it is rejected. Here we only provided the first one since we used the catch method for the purpose (we will talk about handling errors in a minute).

The callback used as the first argument of the then method, takes the fulfillment value of the promise as its argument, which in this case is the Response object. This object, among the others, has a method called json() which we call in the body of the callback. What is this method for? It reads the response stream to its end, and returns itself a promise that resolves with the body of the response being parsed as JSON.

As we know, if a handler function of the then method returns a promise, the fulfillment value of said promise is used as the fulfillment value of the promise returned by the then method itself. This is why the JSON object is available as the argument of the first callback of the second then method in the example. All of the above, happens asynchronously. Here is the result of running the code:

{
  "copyright": "Emilio Rivero Padilla",
  "date": "2019-05-21",
  "explanation": "These three bright nebulae are often featured on telescopic
tours of the constellation Sagittarius and the crowded starfields of the central
Milky Way. In fact, 18th century cosmic tourist Charles Messier cataloged two of
them; M8, the large nebula just left of center, and colorful M20 on the top
left. The third emission region includes NGC 6559 and can be found to the right
of M8. All three are stellar nurseries about five thousand light-years or so
distant. Over a hundred light-years across, the expansive M8 is also known as
the Lagoon Nebula. M20's popular moniker is the Trifid. Glowing hydrogen gas
creates the dominant red color of the emission nebulae. In striking contrast,
blue hues in the Trifid are due to dust reflected starlight. Recently formed
bright blue stars are visible nearby.  The colorful composite skyscape was
recorded in 2018 in Teide National Park in the Canary Islands, Spain.",
  "hdurl": "https://apod.nasa.gov/apod/image/1905/M8M20_Padilla_1534.avif",
  "media_type": "image",
  "service_version": "v1",
  "title": "Deep Field: Nebulae of Sagittarius",
  "url": "https://apod.nasa.gov/apod/image/1905/M8M20_Padilla_960.avif"
}

In the example above we parsed the body of the response as JSON. There are cases in which we want to parse the response body differently. Some methods which can help us in those cases are:

  • Response.blob(): takes a response stream and reads it until it ends. Returns a promise that resolves to a Blob object, which is a file-like object of immutable raw data.
  • Response.text(): reads a response stream and returns a promise that resolves to text, specifically to a USVString object.
  • Response.formData(): reads a response stream and returns a promise that resolves to a FormData object which represents form fields and their values.
  • Response.arrayBuffer(): Reads a response stream and returns a promise that resolves to an ArrayBuffer object, used to represent raw binary data.

Sending more complex requests

The one we saw above was the simplest possibile use case of the fetch method. There are cases in which we need to define and send more complex requests. We have two ways to accomplish the task: the first consists into providing a second parameter to the fetch method, an init object; the second involves the explicit creation of a Request object, which is then passed as an argument to the fetch method. Let’s see both of them.



Providing request settings

Say we want to perform a POST request, sending some data to a specified location. If we want to specify the parameters needed to accomplish said task directly when running the fetch method, we can pass a second argument to it, which is an object that let us apply custom settings to the request. We can write:

fetch('https://httpbin.org/post', {
  method: 'POST',
  headers: new Headers({ 'Content-Type':  'application/json'}),
  body: JSON.stringify({'Name': 'Frodo', 'Lastname': 'Baggins'})
}) 

Just like above, the first argument of the fetch method represents the destination of the request. In this case we send our request to https://httpbin.org/post, which is an endpoint provided by the httbin.org service to test POST requests.

The optional second argument of the function, as we said above, is an object we can use to specify additional parameters for the request. In this case, first of all, we specified the HTTP verb that should be used for the request (POST). After that, we used another interface provided by the fetch API, Headers, which includes methods and properties useful to manipulate requests and response headers. In this case we just set the 'Content-Type' header parameter, declaring the type of content carried by our requests as application/json. Finally, we defined the actual body of the request: we used the stringify method of the JSON object to convert an object to a JSON string.

Running the code above, a POST request is sent to the URL we specified. The httpbin.org service, in this case, returns a response which itself has ‘application/json’ as content type, and describes the data we sent with our request:

fetch('https://httpbin.org/post', {
  method: 'POST',
  headers: { 'Content-Type':  'application/json'},
  body: JSON.stringify({'Name': 'Frodo', 'Lastname': 'Baggins'})
})
.then(response => response.json())
.then(json_object => console.log(json_object))

The result is, as we said above, a description of our request:

{
  "args": {},
  "data": "{\"Name\":\"Frodo\",\"Lastname\":\"Baggins\"}",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "en-US,en;q=0.5",
    "Content-Length": "37",
    "Content-Type": "application/json",
    "Dnt": "1",
    "Host": "httpbin.org",
    "Origin": "http://localhost:8080",
    "Referer": "http://localhost:8080/",
    "User-Agent": "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:66.0)
Gecko/20100101 Firefox/66.0"
  },
  "json": {
    "Lastname": "Baggins",
    "Name": "Frodo"
  },
  "origin": "xx.xx.xx.xx, xx.xx.xx.xx",
  "url": "https://httpbin.org/post"
}

Constructing a Request object manually

As an alternative to the code above, we can create a Request object explicitly, and then pass it to the fetch method:

let request = new Request('https://httpbin.org/post', {
  method: 'POST',
  headers: new Headers({ 'Content-Type':  'application/json'}),
  body: JSON.stringify({'Name': 'Frodo', 'Lastname': 'Baggins'})
})

To send it using fetch, we simply write:

fetch(request)
  .then(response => response.json())
  .then(json_object => console.log(json_object))

Error handling

A fundamental difference between the behavior of the fetch method and JQuery.ajax() is the way a response with an HTTP error status (a status code which is not in the 200-299 range) is handled. In such a case, when using the fetch method, the promise returned by it’s still considered fulfilled. The only case in which the promise is rejected is when there is some communication error and the request can’t reach its destination.



Let’s clarify it with an example. Still using the httpbin.org service, we send a GET request to the the ‘https://httpbin.org/post’ endpoint we used in the previous example, which accepts only POST requests. First we see what happens when using JQuery.ajax():

$.ajax({type: 'get', url: 'https://httpbin.org/post'})
  .then(() => console.log('The promise was fulfilled!'))
  .catch(jqXHR => console.log(`Promise rejected because status code was ${jqXHR.status}`))

The code above returns:

Promise rejected because status code was 405

This indicates that the promise was rejected and therefore the catch method callback was called. When the same request is sent by using the fetch method, the resulting promise is not rejected:

fetch('https://httpbin.org/post')
  .then(response => console.log(`Promise has been fulfilled even if response status is ${response.status}`))
  .catch(reason => console.log('Promise has been rejected!'))

The result of running the above code is:

Promise has been fulfilled even if response status is 405

What happened? Since we used an HTTP verb not allowed for the specified endpoint, we received a response with a Method Not Allowed status code (405). This however, didn’t cause the promise to be rejected, and the callback of the then method was called. If we try the same code changing only the request destination to a non-existent path, ‘https://foo.bar’, the code returns:

Promise has been rejected!

This time, the callback used as argument of the catch method was called. Remembering this behavior is really important: the promise returned by the fetch method is rejected only if the communication with the server fails and the request cannot be completed. To be absolutely sure that our request is successful, we must check the status code of the Response object, which is available in its status property, or test the ok read-only property, which contains a boolean stating if the result was successful or not.

Conclusions

In this tutorial we learned to know the Javascript fetch API, and saw how we can use it as an alternative to other methods of performing asynchronous requests like JQuery.ajax. We saw how to perform basic requests, and how to construct more complex ones. We also examined how the promise returned by the fetch method behaves when a response with a status code out of the 200-299 range is received, and when a connection error happens. To learn more about the fetch API you can consult the Mozilla web docs.



Comments and Discussions
Linux Forum