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
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.jpg",
"media_type": "image",
"service_version": "v1",
"title": "Deep Field: Nebulae of Sagittarius",
"url": "https://apod.nasa.gov/apod/image/1905/M8M20_Padilla_960.jpg"
}
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 aBlob
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 aUSVString
object.Response.formData()
: reads a response stream and returns a promise that resolves to aFormData
object which represents form fields and their values.Response.arrayBuffer()
: Reads a response stream and returns a promise that resolves to anArrayBuffer
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.