A Quick Glance at the Fetch API
Think of a fetch as a simple Javascript Promise. You send a request to the server/API and expect to receive a response. The Fetch Api gives us the fetch() method, which allows us to access those requests and responses, using Javascript.
Let’s look at what a simple fetch looks like:
1 fetch('examples/example.json') 2 .then(response => { 3 **// Here is where you put what you want to do with the response.** 4 }) 5 .catch(error => { 6 console.log(‘Oh No! There was a problem: \n', error); 7 });
What is happening here?
How do we check for a successful response?
Since the fetch promise would only reject a request if it was unable to complete it, we need to manually validate if a response is good and throw an error if it is not.
A generic response from a server looks something similar to this when logged on the console:
1[object Response] { 2 body: (...) 3 bodyUsed: false 4 headers: Headers {} 5 ok: true 6 redirected: false 7 status: 200 8 statusText: "OK" 9 type: "cors" 10 url: "https://assets.breatheco.de/apis/fake/todos/user/Gmihov" 11}
With that in mind, to evaluate the status of a response, you can use its properties: “response.ok”- checks for a status in the 200s and returns a boolean “response.status”- returns an integer with the response status code. Default is 200 “response.statusText”- returns a string whose default is “OK” or a relevant error message
Now we can update the example from above to validate the response
1 fetch('examples/example.json') 2 .then(response => { 3 if (!response.ok) { 4 throw new Error(response.statusText); 5 } 6 // Here is where you put what you want to do with the response. 7 }) 8 .catch(error => { 9 console.log('Looks like there was a problem: \n', error); 10 });
Now what’s happening?
Why do we need this?
To prevent bad responses from going down the chain and breaking your code later on.
We need to throw this error manually because as explained above, error messages received within a response from the server do not register automatically as an error and do not show up in the catch method.
The result will be that the fetch will deliver nothing, and yet the client will be clueless that something has gotten wrong.
Now What?
Now we need to “read” the response in order to access the body of the response.
As you already know, the only data that can travel over an http connection is in plain text format. Therefore, we need to convert the plain text from the body of the response into meaningful Javascript format.
Luckily, there’s a method for that: “response.json();” which we apply to the response.
Next, we update our code to include it.
1 fetch('examples/example.json') 2 .then(response => { 3 if (!response.ok) { 4 throw Error(response.statusText); 5 } 6 // Read the response as json. 7 return response.json(); 8 }) 9 .then(responseAsJson => { 10 // Do stuff with the JSONified response 11 console.log(responseAsJson); 12 }) 13 .catch(error => { 14 console.log('Looks like there was a problem: \n', error); 15 });
Now what is going on?
Simple. Think of it in separate steps.
Fetch the resource at the given path. (Fetch gets the path to the resource and returns a promise that will resolve to a response object).
Then validate the response. (This checks to see if the response is valid (200s). If not, skip to step 5).
Read the response as JSON.
Log the result (The result is being the JSON data received from the body of the response).
Catch any errors.
Now that you have seen the basics, we can compose more advanced fetches.
The default request method is a "GET" method; which is what we have seen so far. The most used methods and what they represent are:
GET: Read/Retrieve POST: Create PUT: Edit/Update DELETE: You guessed it, this simply means Delete.
Here's an example of a post method that is creating a new user:
1fetch('https://example.com/users.json', { 2 method: 'POST', 3 mode: 'cors', 4 redirect: 'follow', 5 headers: new Headers({ 6 'Content-Type': 'text/plain' 7 }) 8}) 9.then(res => res.json()) 10.then(response => { /* handle response */ }) 11.catch(error => console.error(error));
Note that this example fetch is posting (sending to the server) data in plain text format. In modern front end development, this is less common. The most common content type for our methods will be the application/json format, as seen in the following example:
1fetch('https://example.com/users', { 2 method: 'PUT', // or 'POST' 3 body: JSON.stringify(data), // data can be a `string` or an {object} which comes from somewhere further above in our application 4 headers:{ 5 'Content-Type': 'application/json' 6 } 7}) 8.then(res => { 9 if (!res.ok) throw Error(res.statusText); 10 return res.json(); 11}) 12.then(response => console.log('Success:', response)) 13.catch(error => console.error(error));
Did you notice something new above?
The Body of the fetch is where we place the data that we want to send to the server for permanent storage, with our POST or PUT requests. Because we can only send plain text over http, we have to convert our data from its original JS format to a string. We do that with the JSON.stringify() method.
Requests with the GET or DELETE method do not require a body, since normally they are not expected to send any data, however, you can include a body in those requests as well, if needed.
HTTP headers allow us to perform additional actions on the request and response. You can set request headers by using as you see above.
One of the most common headers needed is the 'Content-Type' header. It signals to the recipient of the request (the server), how it should treat the data contained in the body of the request. Because most of the time we send data in some javascript format which is then stringified, we need to instruct the server to convert the string that it receives back into a json format, as seen in line 5 above.
Headers can be sent in a request and received in a response.
Therefore, you can use the headers of the response you receive from the server, to check the content type of its body, and make sure you are receiving the right format before going any further in the process. An example of this would be:
1fetch(https://example.com/users) 2.then(response => { 3 var contentType = response.headers.get("content-type"); 4 if(contentType && contentType.includes("application/json")) { 5 return response.json(); 6 } 7 throw new TypeError("Sorry, There's no JSON here!"); 8 }) 9 .then(jsonifiedResponse => { /* do whatever you want with the jsonified response */ }) 10 .catch(error => console.log(error));
Note that a Header method will throw a TypeError if the name used is not a valid HTTP header name. A list of valid headers can be found here.