2. Fetch
Last updated
Last updated
In the last lesson, we learned about how to handle asynchronous functions with Promises. Today we will learn perhaps the most important asynchronous function, fetch
. The fetch
function lets us request data from a web API
Remember, API stands for "Application Programming Interface". An interface can be thought of as a set of tools or resources. So an API is a set of tools that an application can interact with to create a program. Very generic, right?
The DOM API is one type of API that provides an interface made up of objects and methods that our browser applications can use to dynamically manipulate a web page.
A web API is another type of API whose interface is made up of URLs that each provide access to a different piece of data. To use a web API, instead of calling a function, we send an HTTP(S) request to one of the API's endpoints.
An API endpoint is a URL that provides access to one of the API's resources. Each of the following URLs provides a different set of data (paste them into your browser's URL bar):
https://dog.ceo/api/breeds/list/all
https://dog.ceo/api/breeds/image/random
When we use the HTTPS protocol, we are sending a request over the internet to the dog.ceo servers. Those servers are set up to listen for incoming requests, perform the requested actions according to the endpoint that was targeted, and then send back a response.
The request includes the URL and a CRUD-related verb indicating what you want to do with the requested data:
"POST" - Create
"GET" - Read
"PATCH" - Update
"DELETE" - Delete
Status codes are the three-digit codes that provide information about the response:
200
OK: The request was successful
400
Bad Request: The server received the request but was unable to process it due to malformed syntax
403
Forbidden: The server understood the request but denied it
404
Not Found: The server could not find the requested resource
500
Internal Server Error: The server experienced something unexpected that prevented it from fulfilling the request
But before we get ahead of ourselves, let's look at how to use the fetch
function to send an HTTP request to a web API.
fetch()
functionThe fetch
function is used to send HTTP requests from within our programs. It returns a Promise
object.
When, using the fetch
function, we will follow these 6 steps:
Let's break this down.
Let's start with the first two steps:
Invoke fetch
with an API endpoint (a specific URL provided by a web API). A promise is returned.
Define promise handlers with .then
and .catch
To test this, we can use the API endpoint https://dog.ceo/api/breeds/image/random
and, in the .then()
handler, print out the response
object that the returned promise resolves to.
We should get something that looks like this:
response.body
— a stream of data that we can read to get the response data
response.ok
— indicates if the response succeeded or failed
response.status
— a 3-digit code the type of success or failure that occurred (200
means the requested data was returned)
response.statusText
— a string with more information about the response
response.url
— the endpoint requested
The response.body
is the main thing we want from this response, but as you can see it is a ReadableStream
object, not a string or normal object with the data inside.
Let's look at the next steps to see how we get the data from this ReadableStream
.
Our objective is to get the data from this response so that we can use it in our application. The code below accomplishes that task, but how?
Step 3 is a guard clause for step 4, so let's start with step 4 and then return to step 3:
When an HTTP request is sent to a web API, the response data isn't sent back all at once. Instead, it is sent back in a continuous "stream" of data chunks. That's what the ReadableStream
in the response.body
is.
Reading the incoming data stream is an asynchronous process that we can initiate by invoking the response.json()
function, which will read the data stream as JSON.
response.json()
returns a promise.
However, before we do this, we need to check to see if the response
failed. If it did, then there won't be any data to read in. We don't want to start an asynchronous process unnecessarily if we don't have to.
So, we include a guard clause that checks the response.ok
property and throws a useful error with the status of the response:
So, what we have so far is this:
Note that the ONLY thing that we've changed from the pattern is the API endpoint that we used to invoke fetch()
. Most of this code is "boilerplate" (it will be the same every time).
But why do we return the readingPromise
from the callback given to .then()
?
Remember, the Promise.then()
method returns a Promise. That Promise will resolve to whatever is returned by the callback invoked by .then()
!
So, if we return the readingPromise
from .then()
, then we can handle that Promise by chaining another invocation of .then()
to our first one! When the readingPromise
finishes reading the response.body
data stream, the second .then()
will be invoked with the requested data:
What you do will depend on the API you're using
For example, the dog.ceo API puts the image source URL in the responseBody.message
property.
Every web API should include documentation that outlines the structure of the responseBody
data.
The final step of handling errors isn't that new or interesting. If an error is thrown (or if a Promise rejects), we catch it and print it out. However, we should be aware of what can cause an error (or a rejected Promise).
There are two likely causes of errors / rejected Promises:
A fetch()
Promise rejects when the request itself fails. For example, the URL might be malformed (hxxp://example.com
) or there is a network error (you don't have internet).
A response.json()
Promise rejects when the response
body is NOT in JSON format and therefore cannot be read.
Now that you know how to use APIs, use the 6-step structure above to send and handle fetch
requests to other APIs! Here are a few other free API endpoints that you can try:
https://api.thecatapi.com/v1/images/search
https://pokeapi.co/api/v2/pokemon/pikachu
https://v2.jokeapi.dev/joke/Programming
https://api.sunrise-sunset.org/json?lat=40.7128&lng=-74.0060&tzid=America/New_York
Can you build an application that fetches this data and then displays it in some useful way? We've provided the 2-fetching-challenge
starting template for you to start working with.
HTTP requests are not exactly requests for data; they are requests to perform a CRUD action. Consider these examples related to Instagram:
The client requests to post a new picture on their profile (Create)
The client requests to see all posts made by Beyonce (Read)
The client requests to update a post they made yesterday (Update)
The client requests to delete a post they made yesterday (Delete)
Each of these requests to perform CRUD actions has an HTTP verb that is associated with it.
POST
- Create
GET
- Read
PATCH
- Update
DELETE
- Delete
This HTTP verb or “method” is sent in the HTTP Request so that the server knows what kind of request it is receiving.
The default behavior of using fetch
is to make a GET
request, but we can also make other kinds of requests by defining an options
object. We then pass the options
object as a second argument to fetch
. Here are some examples of POST (create), PATCH (update), and DELETE (delete) requests:
When sending a POST request, we often will need to send a request body. Before sending, we JSON.stringify
that data and tell the API that we're sending "application/json"
data.
Most of the options
object is boilerplate (it's mostly the same each time):
The method
determines the kind of request
The body
determines what we send to the server. Note that it must be JSON.stringify()
-ed first.
The headers
object provides meta-data about the request. It can provide many pieces of information but here all we need to share is the format of the data we are sending to the API.
To see this in action, check out the example in 3-post-requests
which uses a form to create new users:
Every Response that we receive from a server will include a status code indicating how the request was processed. Was the resource found and returned? Was the resource not found? What there an error? Did the POST request successfully create a new resource?
Responses are grouped in five classes:
Informational responses (100 – 199)
Successful responses (200 – 299)
Redirection messages (300 – 399)
Client error responses (400 – 499)
Server error responses (500 – 599)
Important ones to know are 200, 201, 204, 404, and 500
Every URL has a few parts. Understanding those parts can help us fetch precisely the data we want.
Let's break it down:
https://api.sunrise-sunset.org
— This is the host. It tells the client where the resource is hosted/located.
/json
- This is the path. It shows what resource is being requested
?lat=36.7201600&lng=-4.4203400&date=2023-3-15
- These are query parameters and this particular URL has 3: lat
, lng
, and date
. Query parameters begin with a ?
are are separated with &
. Each parameter uses the format name=value
. Try changing the date
parameter!
The returned object is a object. It contains useful information about both the request and the response including:
Using response.json()
assumes that the data is in JSON format (99% of the time it will be). If it isn't, there are .
Note: a fetch()
promise can still resolve but have response.ok
be false
. For example, if the URL is properly formatted but the API you are requesting from is down, the fetch()
call itself will work but response.ok
will be false
. You can check the to see exactly what caused the request to fail. In this case, we manually throw our own Error
in step 3.
Consider this URL which tells us information about the sunrise and sunset at a particular :
When using a new API, make sure to look at that API's (often found at the host address) to understand how to format the request URL.